1 /****************************************************************************
2 * RRDtool 1.3rc7 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;
1616 int leg_y = im->yimg;
1617 int leg_y_prev = im->yimg;
1620 int i, ii, mark = 0;
1621 char prt_fctn; /*special printfunctions */
1622 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1626 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1627 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1628 rrd_set_error("malloc for legspace");
1632 if (im->extra_flags & FULL_SIZE_MODE)
1633 leg_y = leg_y_prev =
1634 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1635 for (i = 0; i < im->gdes_c; i++) {
1637 /* hide legends for rules which are not displayed */
1638 if (im->gdes[i].gf == GF_TEXTALIGN) {
1639 default_txtalign = im->gdes[i].txtalign;
1642 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1643 if (im->gdes[i].gf == GF_HRULE
1644 && (im->gdes[i].yrule <
1645 im->minval || im->gdes[i].yrule > im->maxval))
1646 im->gdes[i].legend[0] = '\0';
1647 if (im->gdes[i].gf == GF_VRULE
1648 && (im->gdes[i].xrule <
1649 im->start || im->gdes[i].xrule > im->end))
1650 im->gdes[i].legend[0] = '\0';
1653 /* turn \\t into tab */
1654 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1655 memmove(tab, tab + 1, strlen(tab));
1658 leg_cc = strlen(im->gdes[i].legend);
1659 /* is there a controle code ant the end of the legend string ? */
1660 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1661 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1663 im->gdes[i].legend[leg_cc] = '\0';
1667 /* only valid control codes */
1668 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1672 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1675 ("Unknown control code at the end of '%s\\%c'",
1676 im->gdes[i].legend, prt_fctn);
1680 if (prt_fctn == 'n') {
1684 /* remove exess space from the end of the legend for \g */
1685 while (prt_fctn == 'g' &&
1686 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1688 im->gdes[i].legend[leg_cc] = '\0';
1693 /* no interleg space if string ends in \g */
1694 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1696 fill += legspace[i];
1699 gfx_get_text_width(im,
1709 im->tabwidth, im->gdes[i].legend);
1714 /* who said there was a special tag ... ? */
1715 if (prt_fctn == 'g') {
1719 if (prt_fctn == '\0') {
1720 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1721 /* just one legend item is left right or center */
1722 switch (default_txtalign) {
1737 /* is it time to place the legends ? */
1738 if (fill > im->ximg - 2 * border) {
1746 if (leg_c == 1 && prt_fctn == 'j') {
1752 if (prt_fctn != '\0') {
1754 if (leg_c >= 2 && prt_fctn == 'j') {
1755 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1759 if (prt_fctn == 'c')
1760 leg_x = (im->ximg - fill) / 2.0;
1761 if (prt_fctn == 'r')
1762 leg_x = im->ximg - fill - border;
1763 for (ii = mark; ii <= i; ii++) {
1764 if (im->gdes[ii].legend[0] == '\0')
1765 continue; /* skip empty legends */
1766 im->gdes[ii].leg_x = leg_x;
1767 im->gdes[ii].leg_y = leg_y;
1769 gfx_get_text_width(im, leg_x,
1778 im->tabwidth, im->gdes[ii].legend)
1783 if (im->extra_flags & FULL_SIZE_MODE) {
1784 /* only add y space if there was text on the line */
1785 if (leg_x > border || prt_fctn == 's')
1786 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1787 if (prt_fctn == 's')
1788 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1790 if (leg_x > border || prt_fctn == 's')
1791 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1792 if (prt_fctn == 's')
1793 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1801 if (im->extra_flags & FULL_SIZE_MODE) {
1802 if (leg_y != leg_y_prev) {
1803 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1805 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1809 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1817 /* create a grid on the graph. it determines what to do
1818 from the values of xsize, start and end */
1820 /* the xaxis labels are determined from the number of seconds per pixel
1821 in the requested graph */
1823 int calc_horizontal_grid(
1831 int decimals, fractionals;
1833 im->ygrid_scale.labfact = 2;
1834 range = im->maxval - im->minval;
1835 scaledrange = range / im->magfact;
1836 /* does the scale of this graph make it impossible to put lines
1837 on it? If so, give up. */
1838 if (isnan(scaledrange)) {
1842 /* find grid spaceing */
1844 if (isnan(im->ygridstep)) {
1845 if (im->extra_flags & ALTYGRID) {
1846 /* find the value with max number of digits. Get number of digits */
1849 (max(fabs(im->maxval), fabs(im->minval)) *
1850 im->viewfactor / im->magfact));
1851 if (decimals <= 0) /* everything is small. make place for zero */
1853 im->ygrid_scale.gridstep =
1855 floor(log10(range * im->viewfactor / im->magfact))) /
1856 im->viewfactor * im->magfact;
1857 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1858 im->ygrid_scale.gridstep = 0.1;
1859 /* should have at least 5 lines but no more then 15 */
1860 if (range / im->ygrid_scale.gridstep < 5
1861 && im->ygrid_scale.gridstep >= 30)
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.gridstep <
1869 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1870 im->ygrid_scale.labfact = 2;
1872 im->ygrid_scale.gridstep /= 5;
1873 im->ygrid_scale.labfact = 5;
1877 (im->ygrid_scale.gridstep *
1878 (double) im->ygrid_scale.labfact * im->viewfactor /
1880 if (fractionals < 0) { /* small amplitude. */
1881 int len = decimals - fractionals + 1;
1883 if (im->unitslength < len + 2)
1884 im->unitslength = len + 2;
1885 sprintf(im->ygrid_scale.labfmt,
1887 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1889 int len = decimals + 1;
1891 if (im->unitslength < len + 2)
1892 im->unitslength = len + 2;
1893 sprintf(im->ygrid_scale.labfmt,
1894 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1896 } else { /* classic rrd grid */
1897 for (i = 0; ylab[i].grid > 0; i++) {
1898 pixel = im->ysize / (scaledrange / ylab[i].grid);
1904 for (i = 0; i < 4; i++) {
1905 if (pixel * ylab[gridind].lfac[i] >=
1906 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1907 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1912 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1915 im->ygrid_scale.gridstep = im->ygridstep;
1916 im->ygrid_scale.labfact = im->ylabfact;
1921 int draw_horizontal_grid(
1927 char graph_label[100];
1929 double X0 = im->xorigin;
1930 double X1 = im->xorigin + im->xsize;
1931 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1932 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1936 im->ygrid_scale.gridstep /
1937 (double) im->magfact * (double) im->viewfactor;
1938 MaxY = scaledstep * (double) egrid;
1939 for (i = sgrid; i <= egrid; i++) {
1941 im->ygrid_scale.gridstep * i);
1943 im->ygrid_scale.gridstep * (i + 1));
1945 if (floor(Y0 + 0.5) >=
1946 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1947 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1948 with the chosen settings. Add a label if required by settings, or if
1949 there is only one label so far and the next grid line is out of bounds. */
1950 if (i % im->ygrid_scale.labfact == 0
1952 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1953 if (im->symbol == ' ') {
1954 if (im->extra_flags & ALTYGRID) {
1955 sprintf(graph_label,
1956 im->ygrid_scale.labfmt,
1957 scaledstep * (double) i);
1960 sprintf(graph_label, "%4.1f",
1961 scaledstep * (double) i);
1963 sprintf(graph_label, "%4.0f",
1964 scaledstep * (double) i);
1968 char sisym = (i == 0 ? ' ' : im->symbol);
1970 if (im->extra_flags & ALTYGRID) {
1971 sprintf(graph_label,
1972 im->ygrid_scale.labfmt,
1973 scaledstep * (double) i, sisym);
1976 sprintf(graph_label, "%4.1f %c",
1977 scaledstep * (double) i, sisym);
1979 sprintf(graph_label, "%4.0f %c",
1980 scaledstep * (double) i, sisym);
1988 text_prop[TEXT_PROP_AXIS].
1990 im->graph_col[GRC_FONT],
1992 text_prop[TEXT_PROP_AXIS].
1995 text_prop[TEXT_PROP_AXIS].
1996 size, im->tabwidth, 0.0,
1997 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
1998 gfx_line(im, X0 - 2, Y0, X0, Y0,
1999 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2000 gfx_line(im, X1, Y0, X1 + 2, Y0,
2001 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2002 gfx_dashed_line(im, X0 - 2, Y0,
2008 im->grid_dash_on, im->grid_dash_off);
2009 } else if (!(im->extra_flags & NOMINOR)) {
2012 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2013 gfx_line(im, X1, Y0, X1 + 2, Y0,
2014 GRIDWIDTH, im->graph_col[GRC_GRID]);
2015 gfx_dashed_line(im, X0 - 1, Y0,
2019 graph_col[GRC_GRID],
2020 im->grid_dash_on, im->grid_dash_off);
2027 /* this is frexp for base 10 */
2038 iexp = floor(log(fabs(x)) / log(10));
2039 mnt = x / pow(10.0, iexp);
2042 mnt = x / pow(10.0, iexp);
2049 /* logaritmic horizontal grid */
2050 int horizontal_log_grid(
2054 double yloglab[][10] = {
2056 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2058 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2060 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2077 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2079 int i, j, val_exp, min_exp;
2080 double nex; /* number of decades in data */
2081 double logscale; /* scale in logarithmic space */
2082 int exfrac = 1; /* decade spacing */
2083 int mid = -1; /* row in yloglab for major grid */
2084 double mspac; /* smallest major grid spacing (pixels) */
2085 int flab; /* first value in yloglab to use */
2086 double value, tmp, pre_value;
2088 char graph_label[100];
2090 nex = log10(im->maxval / im->minval);
2091 logscale = im->ysize / nex;
2092 /* major spacing for data with high dynamic range */
2093 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2100 /* major spacing for less dynamic data */
2102 /* search best row in yloglab */
2104 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2105 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2108 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2111 /* find first value in yloglab */
2113 yloglab[mid][flab] < 10
2114 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2115 if (yloglab[mid][flab] == 10.0) {
2120 if (val_exp % exfrac)
2121 val_exp += abs(-val_exp % exfrac);
2123 X1 = im->xorigin + im->xsize;
2128 value = yloglab[mid][flab] * pow(10.0, val_exp);
2129 if (AlmostEqual2sComplement(value, pre_value, 4))
2130 break; /* it seems we are not converging */
2132 Y0 = ytr(im, value);
2133 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2135 /* major grid line */
2137 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2138 gfx_line(im, X1, Y0, X1 + 2, Y0,
2139 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2140 gfx_dashed_line(im, X0 - 2, Y0,
2145 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2147 if (im->extra_flags & FORCE_UNITS_SI) {
2152 scale = floor(val_exp / 3.0);
2154 pvalue = pow(10.0, val_exp % 3);
2156 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2157 pvalue *= yloglab[mid][flab];
2158 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2159 && ((scale + si_symbcenter) >= 0))
2160 symbol = si_symbol[scale + si_symbcenter];
2163 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2165 sprintf(graph_label, "%3.0e", value);
2169 text_prop[TEXT_PROP_AXIS].
2171 im->graph_col[GRC_FONT],
2173 text_prop[TEXT_PROP_AXIS].
2176 text_prop[TEXT_PROP_AXIS].
2177 size, im->tabwidth, 0.0,
2178 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2180 if (mid < 4 && exfrac == 1) {
2181 /* find first and last minor line behind current major line
2182 * i is the first line and j tha last */
2184 min_exp = val_exp - 1;
2185 for (i = 1; yloglab[mid][i] < 10.0; i++);
2186 i = yloglab[mid][i - 1] + 1;
2190 i = yloglab[mid][flab - 1] + 1;
2191 j = yloglab[mid][flab];
2194 /* draw minor lines below current major line */
2195 for (; i < j; i++) {
2197 value = i * pow(10.0, min_exp);
2198 if (value < im->minval)
2200 Y0 = ytr(im, value);
2201 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2206 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2207 gfx_line(im, X1, Y0, X1 + 2, Y0,
2208 GRIDWIDTH, im->graph_col[GRC_GRID]);
2209 gfx_dashed_line(im, X0 - 1, Y0,
2213 graph_col[GRC_GRID],
2214 im->grid_dash_on, im->grid_dash_off);
2216 } else if (exfrac > 1) {
2217 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2218 value = pow(10.0, i);
2219 if (value < im->minval)
2221 Y0 = ytr(im, value);
2222 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2227 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2228 gfx_line(im, X1, Y0, X1 + 2, Y0,
2229 GRIDWIDTH, im->graph_col[GRC_GRID]);
2230 gfx_dashed_line(im, X0 - 1, Y0,
2234 graph_col[GRC_GRID],
2235 im->grid_dash_on, im->grid_dash_off);
2240 if (yloglab[mid][++flab] == 10.0) {
2246 /* draw minor lines after highest major line */
2247 if (mid < 4 && exfrac == 1) {
2248 /* find first and last minor line below current major line
2249 * i is the first line and j tha last */
2251 min_exp = val_exp - 1;
2252 for (i = 1; yloglab[mid][i] < 10.0; i++);
2253 i = yloglab[mid][i - 1] + 1;
2257 i = yloglab[mid][flab - 1] + 1;
2258 j = yloglab[mid][flab];
2261 /* draw minor lines below current major line */
2262 for (; i < j; i++) {
2264 value = i * pow(10.0, min_exp);
2265 if (value < im->minval)
2267 Y0 = ytr(im, value);
2268 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2272 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2273 gfx_line(im, X1, Y0, X1 + 2, Y0,
2274 GRIDWIDTH, im->graph_col[GRC_GRID]);
2275 gfx_dashed_line(im, X0 - 1, Y0,
2279 graph_col[GRC_GRID],
2280 im->grid_dash_on, im->grid_dash_off);
2283 /* fancy minor gridlines */
2284 else if (exfrac > 1) {
2285 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2286 value = pow(10.0, i);
2287 if (value < im->minval)
2289 Y0 = ytr(im, value);
2290 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2294 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2295 gfx_line(im, X1, Y0, X1 + 2, Y0,
2296 GRIDWIDTH, im->graph_col[GRC_GRID]);
2297 gfx_dashed_line(im, X0 - 1, Y0,
2301 graph_col[GRC_GRID],
2302 im->grid_dash_on, im->grid_dash_off);
2313 int xlab_sel; /* which sort of label and grid ? */
2314 time_t ti, tilab, timajor;
2316 char graph_label[100];
2317 double X0, Y0, Y1; /* points for filled graph and more */
2320 /* the type of time grid is determined by finding
2321 the number of seconds per pixel in the graph */
2322 if (im->xlab_user.minsec == -1) {
2323 factor = (im->end - im->start) / im->xsize;
2325 while (xlab[xlab_sel + 1].minsec !=
2326 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2328 } /* pick the last one */
2329 while (xlab[xlab_sel - 1].minsec ==
2330 xlab[xlab_sel].minsec
2331 && xlab[xlab_sel].length > (im->end - im->start)) {
2333 } /* go back to the smallest size */
2334 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2335 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2336 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2337 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2338 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2339 im->xlab_user.labst = xlab[xlab_sel].labst;
2340 im->xlab_user.precis = xlab[xlab_sel].precis;
2341 im->xlab_user.stst = xlab[xlab_sel].stst;
2344 /* y coords are the same for every line ... */
2346 Y1 = im->yorigin - im->ysize;
2347 /* paint the minor grid */
2348 if (!(im->extra_flags & NOMINOR)) {
2349 for (ti = find_first_time(im->start,
2357 find_first_time(im->start,
2364 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2366 /* are we inside the graph ? */
2367 if (ti < im->start || ti > im->end)
2369 while (timajor < ti) {
2370 timajor = find_next_time(timajor,
2373 mgridtm, im->xlab_user.mgridst);
2376 continue; /* skip as falls on major grid line */
2378 gfx_line(im, X0, Y1 - 2, X0, Y1,
2379 GRIDWIDTH, im->graph_col[GRC_GRID]);
2380 gfx_line(im, X0, Y0, X0, Y0 + 2,
2381 GRIDWIDTH, im->graph_col[GRC_GRID]);
2382 gfx_dashed_line(im, X0, Y0 + 1, X0,
2385 graph_col[GRC_GRID],
2386 im->grid_dash_on, im->grid_dash_off);
2390 /* paint the major grid */
2391 for (ti = find_first_time(im->start,
2399 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2401 /* are we inside the graph ? */
2402 if (ti < im->start || ti > im->end)
2405 gfx_line(im, X0, Y1 - 2, X0, Y1,
2406 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2407 gfx_line(im, X0, Y0, X0, Y0 + 3,
2408 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2409 gfx_dashed_line(im, X0, Y0 + 3, X0,
2413 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2415 /* paint the labels below the graph */
2417 find_first_time(im->start -
2426 im->xlab_user.precis / 2;
2427 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2429 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2430 /* are we inside the graph ? */
2431 if (tilab < im->start || tilab > im->end)
2434 localtime_r(&tilab, &tm);
2435 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2437 # error "your libc has no strftime I guess we'll abort the exercise here."
2442 im->graph_col[GRC_FONT],
2444 text_prop[TEXT_PROP_AXIS].
2447 text_prop[TEXT_PROP_AXIS].
2448 size, im->tabwidth, 0.0,
2449 GFX_H_CENTER, GFX_V_TOP, graph_label);
2458 /* draw x and y axis */
2459 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2460 im->xorigin+im->xsize,im->yorigin-im->ysize,
2461 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2463 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2464 im->xorigin+im->xsize,im->yorigin-im->ysize,
2465 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2467 gfx_line(im, im->xorigin - 4,
2469 im->xorigin + im->xsize +
2470 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2471 gfx_line(im, im->xorigin,
2474 im->yorigin - im->ysize -
2475 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2476 /* arrow for X and Y axis direction */
2477 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 */
2478 im->graph_col[GRC_ARROW]);
2480 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 */
2481 im->graph_col[GRC_ARROW]);
2490 double X0, Y0; /* points for filled graph and more */
2491 struct gfx_color_t water_color;
2493 /* draw 3d border */
2494 gfx_new_area(im, 0, im->yimg,
2495 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2496 gfx_add_point(im, im->ximg - 2, 2);
2497 gfx_add_point(im, im->ximg, 0);
2498 gfx_add_point(im, 0, 0);
2500 gfx_new_area(im, 2, im->yimg - 2,
2502 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2503 gfx_add_point(im, im->ximg, 0);
2504 gfx_add_point(im, im->ximg, im->yimg);
2505 gfx_add_point(im, 0, im->yimg);
2507 if (im->draw_x_grid == 1)
2509 if (im->draw_y_grid == 1) {
2510 if (im->logarithmic) {
2511 res = horizontal_log_grid(im);
2513 res = draw_horizontal_grid(im);
2516 /* dont draw horizontal grid if there is no min and max val */
2518 char *nodata = "No Data found";
2520 gfx_text(im, im->ximg / 2,
2523 im->graph_col[GRC_FONT],
2525 text_prop[TEXT_PROP_AXIS].
2528 text_prop[TEXT_PROP_AXIS].
2529 size, im->tabwidth, 0.0,
2530 GFX_H_CENTER, GFX_V_CENTER, nodata);
2534 /* yaxis unit description */
2539 im->graph_col[GRC_FONT],
2541 text_prop[TEXT_PROP_UNIT].
2544 text_prop[TEXT_PROP_UNIT].
2546 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2550 im->graph_col[GRC_FONT],
2552 text_prop[TEXT_PROP_TITLE].
2555 text_prop[TEXT_PROP_TITLE].
2556 size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2557 /* rrdtool 'logo' */
2558 water_color = im->graph_col[GRC_FONT];
2559 water_color.alpha = 0.3;
2560 gfx_text(im, im->ximg - 4, 5,
2563 text_prop[TEXT_PROP_AXIS].
2564 font, 5.5, im->tabwidth,
2565 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2566 /* graph watermark */
2567 if (im->watermark[0] != '\0') {
2569 im->ximg / 2, im->yimg - 6,
2572 text_prop[TEXT_PROP_AXIS].
2573 font, 5.5, im->tabwidth, 0,
2574 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2578 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2579 for (i = 0; i < im->gdes_c; i++) {
2580 if (im->gdes[i].legend[0] == '\0')
2582 /* im->gdes[i].leg_y is the bottom of the legend */
2583 X0 = im->gdes[i].leg_x;
2584 Y0 = im->gdes[i].leg_y;
2585 gfx_text(im, X0, Y0,
2586 im->graph_col[GRC_FONT],
2589 [TEXT_PROP_LEGEND].font,
2592 [TEXT_PROP_LEGEND].size,
2594 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2595 /* The legend for GRAPH items starts with "M " to have
2596 enough space for the box */
2597 if (im->gdes[i].gf != GF_PRINT &&
2598 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2602 boxH = gfx_get_text_width(im, 0,
2610 size, im->tabwidth, "o") * 1.2;
2612 /* shift the box up a bit */
2614 /* make sure transparent colors show up the same way as in the graph */
2617 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2618 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2620 gfx_new_area(im, X0, Y0 - boxV, X0,
2621 Y0, X0 + boxH, Y0, im->gdes[i].col);
2622 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2625 cairo_new_path(im->cr);
2626 cairo_set_line_width(im->cr, 1.0);
2629 gfx_line_fit(im, &X0, &Y0);
2630 gfx_line_fit(im, &X1, &Y1);
2631 cairo_move_to(im->cr, X0, Y0);
2632 cairo_line_to(im->cr, X1, Y0);
2633 cairo_line_to(im->cr, X1, Y1);
2634 cairo_line_to(im->cr, X0, Y1);
2635 cairo_close_path(im->cr);
2636 cairo_set_source_rgba(im->cr,
2648 blue, im->graph_col[GRC_FRAME].alpha);
2649 if (im->gdes[i].dash) {
2650 /* make box borders in legend dashed if the graph is dashed */
2654 cairo_set_dash(im->cr, dashes, 1, 0.0);
2656 cairo_stroke(im->cr);
2657 cairo_restore(im->cr);
2664 /*****************************************************
2665 * lazy check make sure we rely need to create this graph
2666 *****************************************************/
2673 struct stat imgstat;
2676 return 0; /* no lazy option */
2677 if (strlen(im->graphfile) == 0)
2678 return 0; /* inmemory option */
2679 if (stat(im->graphfile, &imgstat) != 0)
2680 return 0; /* can't stat */
2681 /* one pixel in the existing graph is more then what we would
2683 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2685 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2686 return 0; /* the file does not exist */
2687 switch (im->imgformat) {
2689 size = PngSize(fd, &(im->ximg), &(im->yimg));
2699 int graph_size_location(
2704 /* The actual size of the image to draw is determined from
2705 ** several sources. The size given on the command line is
2706 ** the graph area but we need more as we have to draw labels
2707 ** and other things outside the graph area
2710 int Xvertical = 0, Ytitle =
2711 0, Xylabel = 0, Xmain = 0, Ymain =
2712 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2714 if (im->extra_flags & ONLY_GRAPH) {
2716 im->ximg = im->xsize;
2717 im->yimg = im->ysize;
2718 im->yorigin = im->ysize;
2723 /** +---+--------------------------------------------+
2724 ** | y |...............graph title..................|
2725 ** | +---+-------------------------------+--------+
2728 ** | i | a | | pie |
2729 ** | s | x | main graph area | chart |
2734 ** | l | b +-------------------------------+--------+
2735 ** | e | l | x axis labels | |
2736 ** +---+---+-------------------------------+--------+
2737 ** |....................legends.....................|
2738 ** +------------------------------------------------+
2740 ** +------------------------------------------------+
2743 if (im->ylegend[0] != '\0') {
2744 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2747 if (im->title[0] != '\0') {
2748 /* The title is placed "inbetween" two text lines so it
2749 ** automatically has some vertical spacing. The horizontal
2750 ** spacing is added here, on each side.
2752 /* if necessary, reduce the font size of the title until it fits the image width */
2753 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2757 if (im->draw_x_grid) {
2758 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2760 if (im->draw_y_grid || im->forceleftspace) {
2762 gfx_get_text_width(im, 0,
2770 size, im->tabwidth, "0") * im->unitslength;
2774 if (im->extra_flags & FULL_SIZE_MODE) {
2775 /* The actual size of the image to draw has been determined by the user.
2776 ** The graph area is the space remaining after accounting for the legend,
2777 ** the watermark, the pie chart, the axis labels, and the title.
2780 im->ximg = im->xsize;
2781 im->yimg = im->ysize;
2782 im->yorigin = im->ysize;
2785 im->yorigin += Ytitle;
2786 /* Now calculate the total size. Insert some spacing where
2787 desired. im->xorigin and im->yorigin need to correspond
2788 with the lower left corner of the main graph area or, if
2789 this one is not set, the imaginary box surrounding the
2791 /* Initial size calculation for the main graph area */
2792 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2794 Xmain -= Xspacing; /* put space between main graph area and right edge */
2795 im->xorigin = Xspacing + Xylabel;
2796 /* the length of the title should not influence with width of the graph
2797 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2798 if (Xvertical) { /* unit description */
2800 im->xorigin += Xvertical;
2804 /* The vertical size of the image is known in advance. The main graph area
2805 ** (Ymain) and im->yorigin must be set according to the space requirements
2806 ** of the legend and the axis labels.
2808 if (im->extra_flags & NOLEGEND) {
2809 /* set dimensions correctly if using full size mode with no legend */
2812 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2813 Ymain = im->yorigin;
2815 /* Determine where to place the legends onto the image.
2816 ** Set Ymain and adjust im->yorigin to match the space requirements.
2818 if (leg_place(im, &Ymain) == -1)
2823 /* remove title space *or* some padding above the graph from the main graph area */
2827 Ymain -= 1.5 * Yspacing;
2830 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2831 if (im->watermark[0] != '\0') {
2832 Ymain -= Ywatermark;
2836 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2838 /* The actual size of the image to draw is determined from
2839 ** several sources. The size given on the command line is
2840 ** the graph area but we need more as we have to draw labels
2841 ** and other things outside the graph area.
2844 if (im->ylegend[0] != '\0') {
2845 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2849 if (im->title[0] != '\0') {
2850 /* The title is placed "inbetween" two text lines so it
2851 ** automatically has some vertical spacing. The horizontal
2852 ** spacing is added here, on each side.
2854 /* don't care for the with of the title
2855 Xtitle = gfx_get_text_width(im->canvas, 0,
2856 im->text_prop[TEXT_PROP_TITLE].font,
2857 im->text_prop[TEXT_PROP_TITLE].size,
2859 im->title, 0) + 2*Xspacing; */
2860 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2867 /* Now calculate the total size. Insert some spacing where
2868 desired. im->xorigin and im->yorigin need to correspond
2869 with the lower left corner of the main graph area or, if
2870 this one is not set, the imaginary box surrounding the
2873 /* The legend width cannot yet be determined, as a result we
2874 ** have problems adjusting the image to it. For now, we just
2875 ** forget about it at all; the legend will have to fit in the
2876 ** size already allocated.
2878 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2880 im->ximg += Xspacing;
2881 im->xorigin = Xspacing + Xylabel;
2882 /* the length of the title should not influence with width of the graph
2883 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2884 if (Xvertical) { /* unit description */
2885 im->ximg += Xvertical;
2886 im->xorigin += Xvertical;
2889 /* The vertical size is interesting... we need to compare
2890 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2891 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2892 ** in order to start even thinking about Ylegend or Ywatermark.
2894 ** Do it in three portions: First calculate the inner part,
2895 ** then do the legend, then adjust the total height of the img,
2896 ** adding space for a watermark if one exists;
2898 /* reserve space for main and/or pie */
2899 im->yimg = Ymain + Yxlabel;
2900 im->yorigin = im->yimg - Yxlabel;
2901 /* reserve space for the title *or* some padding above the graph */
2904 im->yorigin += Ytitle;
2906 im->yimg += 1.5 * Yspacing;
2907 im->yorigin += 1.5 * Yspacing;
2909 /* reserve space for padding below the graph */
2910 im->yimg += Yspacing;
2911 /* Determine where to place the legends onto the image.
2912 ** Adjust im->yimg to match the space requirements.
2914 if (leg_place(im, 0) == -1)
2916 if (im->watermark[0] != '\0') {
2917 im->yimg += Ywatermark;
2925 static cairo_status_t cairo_output(
2929 unsigned int length)
2931 image_desc_t *im = closure;
2933 im->rendered_image =
2934 realloc(im->rendered_image, im->rendered_image_size + length);
2935 if (im->rendered_image == NULL)
2936 return CAIRO_STATUS_WRITE_ERROR;
2937 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2938 im->rendered_image_size += length;
2939 return CAIRO_STATUS_SUCCESS;
2942 /* draw that picture thing ... */
2947 int lazy = lazy_check(im);
2948 double areazero = 0.0;
2949 graph_desc_t *lastgdes = NULL;
2951 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2953 /* if we are lazy and there is nothing to PRINT ... quit now */
2954 if (lazy && im->prt_c == 0)
2956 /* pull the data from the rrd files ... */
2957 if (data_fetch(im) == -1)
2959 /* evaluate VDEF and CDEF operations ... */
2960 if (data_calc(im) == -1)
2962 /* calculate and PRINT and GPRINT definitions. We have to do it at
2963 * this point because it will affect the length of the legends
2964 * if there are no graph elements we stop here ...
2965 * if we are lazy, try to quit ...
2970 if ((i == 0) || lazy)
2972 /**************************************************************
2973 *** Calculating sizes and locations became a bit confusing ***
2974 *** so I moved this into a separate function. ***
2975 **************************************************************/
2976 if (graph_size_location(im, i) == -1)
2979 info.u_cnt = im->xorigin;
2980 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2981 info.u_cnt = im->yorigin - im->ysize;
2982 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2983 info.u_cnt = im->xsize;
2984 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2985 info.u_cnt = im->ysize;
2986 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2987 info.u_cnt = im->ximg;
2988 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2989 info.u_cnt = im->yimg;
2990 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2992 /* get actual drawing data and find min and max values */
2993 if (data_proc(im) == -1)
2995 if (!im->logarithmic) {
2999 /* identify si magnitude Kilo, Mega Giga ? */
3000 if (!im->rigid && !im->logarithmic)
3001 expand_range(im); /* make sure the upper and lower limit are
3004 info.u_val = im->minval;
3005 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3006 info.u_val = im->maxval;
3007 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3009 if (!calc_horizontal_grid(im))
3014 apply_gridfit(im); */
3015 /* the actual graph is created by going through the individual
3016 graph elements and then drawing them */
3017 cairo_surface_destroy(im->surface);
3018 switch (im->imgformat) {
3021 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3022 im->ximg * im->zoom,
3023 im->yimg * im->zoom);
3027 im->surface = strlen(im->graphfile)
3028 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3029 im->yimg * im->zoom)
3030 : cairo_pdf_surface_create_for_stream
3031 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3035 im->surface = strlen(im->graphfile)
3037 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3038 im->yimg * im->zoom)
3039 : cairo_ps_surface_create_for_stream
3040 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3044 im->surface = strlen(im->graphfile)
3046 cairo_svg_surface_create(im->
3048 im->ximg * im->zoom, im->yimg * im->zoom)
3049 : cairo_svg_surface_create_for_stream
3050 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3051 cairo_svg_surface_restrict_to_version
3052 (im->surface, CAIRO_SVG_VERSION_1_1);
3055 im->cr = cairo_create(im->surface);
3056 cairo_set_antialias(im->cr, im->graph_antialias);
3057 cairo_scale(im->cr, im->zoom, im->zoom);
3058 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3059 gfx_new_area(im, 0, 0, 0, im->yimg,
3060 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3061 gfx_add_point(im, im->ximg, 0);
3063 gfx_new_area(im, im->xorigin,
3066 im->xsize, im->yorigin,
3069 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3070 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3072 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3073 im->xsize, im->ysize + 2.0);
3075 if (im->minval > 0.0)
3076 areazero = im->minval;
3077 if (im->maxval < 0.0)
3078 areazero = im->maxval;
3079 for (i = 0; i < im->gdes_c; i++) {
3080 switch (im->gdes[i].gf) {
3094 for (ii = 0; ii < im->xsize; ii++) {
3095 if (!isnan(im->gdes[i].p_data[ii])
3096 && im->gdes[i].p_data[ii] != 0.0) {
3097 if (im->gdes[i].yrule > 0) {
3104 im->ysize, 1.0, im->gdes[i].col);
3105 } else if (im->gdes[i].yrule < 0) {
3108 im->yorigin - im->ysize,
3113 im->ysize, 1.0, im->gdes[i].col);
3120 /* fix data points at oo and -oo */
3121 for (ii = 0; ii < im->xsize; ii++) {
3122 if (isinf(im->gdes[i].p_data[ii])) {
3123 if (im->gdes[i].p_data[ii] > 0) {
3124 im->gdes[i].p_data[ii] = im->maxval;
3126 im->gdes[i].p_data[ii] = im->minval;
3132 /* *******************************************************
3137 -------|--t-1--t--------------------------------
3139 if we know the value at time t was a then
3140 we draw a square from t-1 to t with the value a.
3142 ********************************************************* */
3143 if (im->gdes[i].col.alpha != 0.0) {
3144 /* GF_LINE and friend */
3145 if (im->gdes[i].gf == GF_LINE) {
3146 double last_y = 0.0;
3150 cairo_new_path(im->cr);
3151 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3152 if (im->gdes[i].dash) {
3153 cairo_set_dash(im->cr,
3154 im->gdes[i].p_dashes,
3155 im->gdes[i].ndash, im->gdes[i].offset);
3158 for (ii = 1; ii < im->xsize; ii++) {
3159 if (isnan(im->gdes[i].p_data[ii])
3160 || (im->slopemode == 1
3161 && isnan(im->gdes[i].p_data[ii - 1]))) {
3166 last_y = ytr(im, im->gdes[i].p_data[ii]);
3167 if (im->slopemode == 0) {
3168 double x = ii - 1 + im->xorigin;
3171 gfx_line_fit(im, &x, &y);
3172 cairo_move_to(im->cr, x, y);
3173 x = ii + im->xorigin;
3175 gfx_line_fit(im, &x, &y);
3176 cairo_line_to(im->cr, x, y);
3178 double x = ii - 1 + im->xorigin;
3180 ytr(im, im->gdes[i].p_data[ii - 1]);
3181 gfx_line_fit(im, &x, &y);
3182 cairo_move_to(im->cr, x, y);
3183 x = ii + im->xorigin;
3185 gfx_line_fit(im, &x, &y);
3186 cairo_line_to(im->cr, x, y);
3190 double x1 = ii + im->xorigin;
3191 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3193 if (im->slopemode == 0
3194 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3195 double x = ii - 1 + im->xorigin;
3198 gfx_line_fit(im, &x, &y);
3199 cairo_line_to(im->cr, x, y);
3202 gfx_line_fit(im, &x1, &y1);
3203 cairo_line_to(im->cr, x1, y1);
3206 cairo_set_source_rgba(im->cr,
3212 col.blue, im->gdes[i].col.alpha);
3213 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3214 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3215 cairo_stroke(im->cr);
3216 cairo_restore(im->cr);
3220 (double *) malloc(sizeof(double) * im->xsize * 2);
3222 (double *) malloc(sizeof(double) * im->xsize * 2);
3224 (double *) malloc(sizeof(double) * im->xsize * 2);
3226 (double *) malloc(sizeof(double) * im->xsize * 2);
3229 for (ii = 0; ii <= im->xsize; ii++) {
3232 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3238 AlmostEqual2sComplement(foreY
3242 AlmostEqual2sComplement(foreY
3252 foreY[cntI], im->gdes[i].col);
3253 while (cntI < idxI) {
3258 AlmostEqual2sComplement(foreY
3262 AlmostEqual2sComplement(foreY
3269 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3271 gfx_add_point(im, backX[idxI], backY[idxI]);
3277 AlmostEqual2sComplement(backY
3281 AlmostEqual2sComplement(backY
3288 gfx_add_point(im, backX[idxI], backY[idxI]);
3298 if (ii == im->xsize)
3300 if (im->slopemode == 0 && ii == 0) {
3303 if (isnan(im->gdes[i].p_data[ii])) {
3307 ytop = ytr(im, im->gdes[i].p_data[ii]);
3308 if (lastgdes && im->gdes[i].stack) {
3309 ybase = ytr(im, lastgdes->p_data[ii]);
3311 ybase = ytr(im, areazero);
3313 if (ybase == ytop) {
3319 double extra = ytop;
3324 if (im->slopemode == 0) {
3325 backY[++idxI] = ybase - 0.2;
3326 backX[idxI] = ii + im->xorigin - 1;
3327 foreY[idxI] = ytop + 0.2;
3328 foreX[idxI] = ii + im->xorigin - 1;
3330 backY[++idxI] = ybase - 0.2;
3331 backX[idxI] = ii + im->xorigin;
3332 foreY[idxI] = ytop + 0.2;
3333 foreX[idxI] = ii + im->xorigin;
3335 /* close up any remaining area */
3340 } /* else GF_LINE */
3342 /* if color != 0x0 */
3343 /* make sure we do not run into trouble when stacking on NaN */
3344 for (ii = 0; ii < im->xsize; ii++) {
3345 if (isnan(im->gdes[i].p_data[ii])) {
3346 if (lastgdes && (im->gdes[i].stack)) {
3347 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3349 im->gdes[i].p_data[ii] = areazero;
3353 lastgdes = &(im->gdes[i]);
3357 ("STACK should already be turned into LINE or AREA here");
3362 cairo_reset_clip(im->cr);
3364 /* grid_paint also does the text */
3365 if (!(im->extra_flags & ONLY_GRAPH))
3367 if (!(im->extra_flags & ONLY_GRAPH))
3369 /* the RULES are the last thing to paint ... */
3370 for (i = 0; i < im->gdes_c; i++) {
3372 switch (im->gdes[i].gf) {
3374 if (im->gdes[i].yrule >= im->minval
3375 && im->gdes[i].yrule <= im->maxval) {
3377 if (im->gdes[i].dash) {
3378 cairo_set_dash(im->cr,
3379 im->gdes[i].p_dashes,
3380 im->gdes[i].ndash, im->gdes[i].offset);
3382 gfx_line(im, im->xorigin,
3383 ytr(im, im->gdes[i].yrule),
3384 im->xorigin + im->xsize,
3385 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3386 cairo_stroke(im->cr);
3387 cairo_restore(im->cr);
3391 if (im->gdes[i].xrule >= im->start
3392 && im->gdes[i].xrule <= im->end) {
3394 if (im->gdes[i].dash) {
3395 cairo_set_dash(im->cr,
3396 im->gdes[i].p_dashes,
3397 im->gdes[i].ndash, im->gdes[i].offset);
3400 xtr(im, im->gdes[i].xrule),
3401 im->yorigin, xtr(im,
3405 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3406 cairo_stroke(im->cr);
3407 cairo_restore(im->cr);
3416 switch (im->imgformat) {
3419 cairo_status_t status;
3421 status = strlen(im->graphfile) ?
3422 cairo_surface_write_to_png(im->surface, im->graphfile)
3423 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3426 if (status != CAIRO_STATUS_SUCCESS) {
3427 rrd_set_error("Could not save png to '%s'", im->graphfile);
3433 if (strlen(im->graphfile)) {
3434 cairo_show_page(im->cr);
3436 cairo_surface_finish(im->surface);
3445 /*****************************************************
3447 *****************************************************/
3454 if ((im->gdes = (graph_desc_t *)
3455 rrd_realloc(im->gdes, (im->gdes_c)
3456 * sizeof(graph_desc_t))) == NULL) {
3457 rrd_set_error("realloc graph_descs");
3462 im->gdes[im->gdes_c - 1].step = im->step;
3463 im->gdes[im->gdes_c - 1].step_orig = im->step;
3464 im->gdes[im->gdes_c - 1].stack = 0;
3465 im->gdes[im->gdes_c - 1].linewidth = 0;
3466 im->gdes[im->gdes_c - 1].debug = 0;
3467 im->gdes[im->gdes_c - 1].start = im->start;
3468 im->gdes[im->gdes_c - 1].start_orig = im->start;
3469 im->gdes[im->gdes_c - 1].end = im->end;
3470 im->gdes[im->gdes_c - 1].end_orig = im->end;
3471 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3472 im->gdes[im->gdes_c - 1].data = NULL;
3473 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3474 im->gdes[im->gdes_c - 1].data_first = 0;
3475 im->gdes[im->gdes_c - 1].p_data = NULL;
3476 im->gdes[im->gdes_c - 1].rpnp = NULL;
3477 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3478 im->gdes[im->gdes_c - 1].shift = 0.0;
3479 im->gdes[im->gdes_c - 1].dash = 0;
3480 im->gdes[im->gdes_c - 1].ndash = 0;
3481 im->gdes[im->gdes_c - 1].offset = 0;
3482 im->gdes[im->gdes_c - 1].col.red = 0.0;
3483 im->gdes[im->gdes_c - 1].col.green = 0.0;
3484 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3485 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3486 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3487 im->gdes[im->gdes_c - 1].format[0] = '\0';
3488 im->gdes[im->gdes_c - 1].strftm = 0;
3489 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3490 im->gdes[im->gdes_c - 1].ds = -1;
3491 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3492 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3493 im->gdes[im->gdes_c - 1].yrule = DNAN;
3494 im->gdes[im->gdes_c - 1].xrule = 0;
3498 /* copies input untill the first unescaped colon is found
3499 or until input ends. backslashes have to be escaped as well */
3501 const char *const input,
3507 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3508 if (input[inp] == '\\'
3509 && input[inp + 1] != '\0'
3510 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3511 output[outp++] = input[++inp];
3513 output[outp++] = input[inp];
3516 output[outp] = '\0';
3520 /* Now just a wrapper around rrd_graph_v */
3532 info_t *grinfo = NULL;
3535 grinfo = rrd_graph_v(argc, argv);
3541 if (strcmp(walker->key, "image_info") == 0) {
3544 rrd_realloc((*prdata),
3545 (prlines + 1) * sizeof(char *))) == NULL) {
3546 rrd_set_error("realloc prdata");
3549 /* imginfo goes to position 0 in the prdata array */
3550 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3551 + 2) * sizeof(char));
3552 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3553 (*prdata)[prlines] = NULL;
3555 /* skip anything else */
3556 walker = walker->next;
3560 if (strcmp(walker->key, "image_width") == 0) {
3561 *xsize = walker->value.u_int;
3562 } else if (strcmp(walker->key, "image_height") == 0) {
3563 *ysize = walker->value.u_int;
3564 } else if (strcmp(walker->key, "value_min") == 0) {
3565 *ymin = walker->value.u_val;
3566 } else if (strcmp(walker->key, "value_max") == 0) {
3567 *ymax = walker->value.u_val;
3568 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3571 rrd_realloc((*prdata),
3572 (prlines + 1) * sizeof(char *))) == NULL) {
3573 rrd_set_error("realloc prdata");
3576 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3577 + 2) * sizeof(char));
3578 (*prdata)[prlines] = NULL;
3579 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3580 } else if (strcmp(walker->key, "image") == 0) {
3581 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3582 (stream ? stream : stdout));
3584 /* skip anything else */
3585 walker = walker->next;
3592 /* Some surgery done on this function, it became ridiculously big.
3594 ** - initializing now in rrd_graph_init()
3595 ** - options parsing now in rrd_graph_options()
3596 ** - script parsing now in rrd_graph_script()
3598 info_t *rrd_graph_v(
3605 rrd_graph_init(&im);
3606 /* a dummy surface so that we can measure text sizes for placements */
3607 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3608 im.cr = cairo_create(im.surface);
3609 rrd_graph_options(argc, argv, &im);
3610 if (rrd_test_error()) {
3611 info_free(im.grinfo);
3616 if (optind >= argc) {
3617 info_free(im.grinfo);
3619 rrd_set_error("missing filename");
3623 if (strlen(argv[optind]) >= MAXPATH) {
3624 rrd_set_error("filename (including path) too long");
3625 info_free(im.grinfo);
3630 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3631 im.graphfile[MAXPATH - 1] = '\0';
3633 if (strcmp(im.graphfile, "-") == 0) {
3634 im.graphfile[0] = '\0';
3637 rrd_graph_script(argc, argv, &im, 1);
3638 if (rrd_test_error()) {
3639 info_free(im.grinfo);
3644 /* Everything is now read and the actual work can start */
3646 if (graph_paint(&im) == -1) {
3647 info_free(im.grinfo);
3653 /* The image is generated and needs to be output.
3654 ** Also, if needed, print a line with information about the image.
3661 sprintf_alloc(im.imginfo,
3664 im.ximg), (long) (im.zoom * im.yimg));
3665 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3668 if (im.rendered_image) {
3671 img.u_blo.size = im.rendered_image_size;
3672 img.u_blo.ptr = im.rendered_image;
3673 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3680 void rrd_graph_init(
3689 #ifdef HAVE_SETLOCALE
3690 setlocale(LC_TIME, "");
3691 #ifdef HAVE_MBSTOWCS
3692 setlocale(LC_CTYPE, "");
3697 im->draw_x_grid = 1;
3698 im->draw_y_grid = 1;
3699 im->extra_flags = 0;
3700 im->font_options = cairo_font_options_create();
3701 im->forceleftspace = 0;
3704 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3705 im->grid_dash_off = 1;
3706 im->grid_dash_on = 1;
3708 im->grinfo = (info_t *) NULL;
3709 im->grinfo_current = (info_t *) NULL;
3710 im->imgformat = IF_PNG;
3713 im->logarithmic = 0;
3719 im->rendered_image_size = 0;
3720 im->rendered_image = NULL;
3725 im->tabwidth = 40.0;
3726 im->title[0] = '\0';
3727 im->unitsexponent = 9999;
3728 im->unitslength = 6;
3729 im->viewfactor = 1.0;
3730 im->watermark[0] = '\0';
3731 im->with_markup = 0;
3733 im->xlab_user.minsec = -1;
3736 im->ygridstep = DNAN;
3738 im->ylegend[0] = '\0';
3742 cairo_font_options_set_hint_style
3743 (im->font_options, CAIRO_HINT_STYLE_FULL);
3744 cairo_font_options_set_hint_metrics
3745 (im->font_options, CAIRO_HINT_METRICS_ON);
3746 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3747 for (i = 0; i < DIM(graph_col); i++)
3748 im->graph_col[i] = graph_col[i];
3749 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3752 char rrd_win_default_font[1000];
3754 windir = getenv("windir");
3755 /* %windir% is something like D:\windows or C:\winnt */
3756 if (windir != NULL) {
3757 strncpy(rrd_win_default_font, windir, 500);
3758 rrd_win_default_font[500] = '\0';
3759 strcat(rrd_win_default_font, "\\fonts\\");
3760 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3761 for (i = 0; i < DIM(text_prop); i++) {
3762 strncpy(text_prop[i].font,
3763 rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3764 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3772 deffont = getenv("RRD_DEFAULT_FONT");
3773 if (deffont != NULL) {
3774 for (i = 0; i < DIM(text_prop); i++) {
3775 strncpy(text_prop[i].font, deffont,
3776 sizeof(text_prop[i].font) - 1);
3777 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3781 for (i = 0; i < DIM(text_prop); i++) {
3782 im->text_prop[i].size = text_prop[i].size;
3783 strcpy(im->text_prop[i].font, text_prop[i].font);
3787 void rrd_graph_options(
3794 char *parsetime_error = NULL;
3795 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3796 time_t start_tmp = 0, end_tmp = 0;
3798 struct rrd_time_value start_tv, end_tv;
3799 long unsigned int color;
3800 char *old_locale = "";
3802 /* defines for long options without a short equivalent. should be bytes,
3803 and may not collide with (the ASCII value of) short options */
3804 #define LONGOPT_UNITS_SI 255
3807 struct option long_options[] = {
3808 { "start", required_argument, 0, 's'},
3809 { "end", required_argument, 0, 'e'},
3810 { "x-grid", required_argument, 0, 'x'},
3811 { "y-grid", required_argument, 0, 'y'},
3812 { "vertical-label", required_argument, 0, 'v'},
3813 { "width", required_argument, 0, 'w'},
3814 { "height", required_argument, 0, 'h'},
3815 { "full-size-mode", no_argument, 0, 'D'},
3816 { "interlaced", no_argument, 0, 'i'},
3817 { "upper-limit", required_argument, 0, 'u'},
3818 { "lower-limit", required_argument, 0, 'l'},
3819 { "rigid", no_argument, 0, 'r'},
3820 { "base", required_argument, 0, 'b'},
3821 { "logarithmic", no_argument, 0, 'o'},
3822 { "color", required_argument, 0, 'c'},
3823 { "font", required_argument, 0, 'n'},
3824 { "title", required_argument, 0, 't'},
3825 { "imginfo", required_argument, 0, 'f'},
3826 { "imgformat", required_argument, 0, 'a'},
3827 { "lazy", no_argument, 0, 'z'},
3828 { "zoom", required_argument, 0, 'm'},
3829 { "no-legend", no_argument, 0, 'g'},
3830 { "force-rules-legend", no_argument, 0, 'F'},
3831 { "only-graph", no_argument, 0, 'j'},
3832 { "alt-y-grid", no_argument, 0, 'Y'},
3833 { "no-minor", no_argument, 0, 'I'},
3834 { "slope-mode", no_argument, 0, 'E'},
3835 { "alt-autoscale", no_argument, 0, 'A'},
3836 { "alt-autoscale-min", no_argument, 0, 'J'},
3837 { "alt-autoscale-max", no_argument, 0, 'M'},
3838 { "no-gridfit", no_argument, 0, 'N'},
3839 { "units-exponent", required_argument, 0, 'X'},
3840 { "units-length", required_argument, 0, 'L'},
3841 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3842 { "step", required_argument, 0, 'S'},
3843 { "tabwidth", required_argument, 0, 'T'},
3844 { "font-render-mode", required_argument, 0, 'R'},
3845 { "graph-render-mode", required_argument, 0, 'G'},
3846 { "font-smoothing-threshold", required_argument, 0, 'B'},
3847 { "watermark", required_argument, 0, 'W'},
3848 { "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 */
3849 { "pango-markup", no_argument, 0, 'P'},
3855 opterr = 0; /* initialize getopt */
3856 parsetime("end-24h", &start_tv);
3857 parsetime("now", &end_tv);
3859 int option_index = 0;
3861 int col_start, col_end;
3863 opt = getopt_long(argc, argv,
3864 "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",
3865 long_options, &option_index);
3870 im->extra_flags |= NOMINOR;
3873 im->extra_flags |= ALTYGRID;
3876 im->extra_flags |= ALTAUTOSCALE;
3879 im->extra_flags |= ALTAUTOSCALE_MIN;
3882 im->extra_flags |= ALTAUTOSCALE_MAX;
3885 im->extra_flags |= ONLY_GRAPH;
3888 im->extra_flags |= NOLEGEND;
3891 im->extra_flags |= FORCE_RULES_LEGEND;
3893 case LONGOPT_UNITS_SI:
3894 if (im->extra_flags & FORCE_UNITS) {
3895 rrd_set_error("--units can only be used once!");
3896 setlocale(LC_NUMERIC, old_locale);
3899 if (strcmp(optarg, "si") == 0)
3900 im->extra_flags |= FORCE_UNITS_SI;
3902 rrd_set_error("invalid argument for --units: %s", optarg);
3907 im->unitsexponent = atoi(optarg);
3910 im->unitslength = atoi(optarg);
3911 im->forceleftspace = 1;
3914 old_locale = setlocale(LC_NUMERIC, "C");
3915 im->tabwidth = atof(optarg);
3916 setlocale(LC_NUMERIC, old_locale);
3919 old_locale = setlocale(LC_NUMERIC, "C");
3920 im->step = atoi(optarg);
3921 setlocale(LC_NUMERIC, old_locale);
3927 im->with_markup = 1;
3930 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3931 rrd_set_error("start time: %s", parsetime_error);
3936 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3937 rrd_set_error("end time: %s", parsetime_error);
3942 if (strcmp(optarg, "none") == 0) {
3943 im->draw_x_grid = 0;
3947 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3949 &im->xlab_user.gridst,
3951 &im->xlab_user.mgridst,
3953 &im->xlab_user.labst,
3954 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3955 strncpy(im->xlab_form, optarg + stroff,
3956 sizeof(im->xlab_form) - 1);
3957 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3959 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3960 rrd_set_error("unknown keyword %s", scan_gtm);
3963 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3965 rrd_set_error("unknown keyword %s", scan_mtm);
3968 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
3969 rrd_set_error("unknown keyword %s", scan_ltm);
3972 im->xlab_user.minsec = 1;
3973 im->xlab_user.stst = im->xlab_form;
3975 rrd_set_error("invalid x-grid format");
3981 if (strcmp(optarg, "none") == 0) {
3982 im->draw_y_grid = 0;
3985 old_locale = setlocale(LC_NUMERIC, "C");
3986 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3987 setlocale(LC_NUMERIC, old_locale);
3988 if (im->ygridstep <= 0) {
3989 rrd_set_error("grid step must be > 0");
3991 } else if (im->ylabfact < 1) {
3992 rrd_set_error("label factor must be > 0");
3996 setlocale(LC_NUMERIC, old_locale);
3997 rrd_set_error("invalid y-grid format");
4002 strncpy(im->ylegend, optarg, 150);
4003 im->ylegend[150] = '\0';
4006 old_locale = setlocale(LC_NUMERIC, "C");
4007 im->maxval = atof(optarg);
4008 setlocale(LC_NUMERIC, old_locale);
4011 old_locale = setlocale(LC_NUMERIC, "C");
4012 im->minval = atof(optarg);
4013 setlocale(LC_NUMERIC, old_locale);
4016 im->base = atol(optarg);
4017 if (im->base != 1024 && im->base != 1000) {
4019 ("the only sensible value for base apart from 1000 is 1024");
4024 long_tmp = atol(optarg);
4025 if (long_tmp < 10) {
4026 rrd_set_error("width below 10 pixels");
4029 im->xsize = long_tmp;
4032 long_tmp = atol(optarg);
4033 if (long_tmp < 10) {
4034 rrd_set_error("height below 10 pixels");
4037 im->ysize = long_tmp;
4040 im->extra_flags |= FULL_SIZE_MODE;
4043 /* interlaced png not supported at the moment */
4049 im->imginfo = optarg;
4053 (im->imgformat = if_conv(optarg)) == -1) {
4054 rrd_set_error("unsupported graphics format '%s'", optarg);
4065 im->logarithmic = 1;
4069 "%10[A-Z]#%n%8lx%n",
4070 col_nam, &col_start, &color, &col_end) == 2) {
4072 int col_len = col_end - col_start;
4077 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4085 (((color & 0xF000) *
4086 0x11000) | ((color & 0x0F00) *
4087 0x01100) | ((color &
4090 ((color & 0x000F) * 0x00011)
4094 color = (color << 8) + 0xff /* shift left by 8 */ ;
4099 rrd_set_error("the color format is #RRGGBB[AA]");
4102 if ((ci = grc_conv(col_nam)) != -1) {
4103 im->graph_col[ci] = gfx_hex_to_col(color);
4105 rrd_set_error("invalid color name '%s'", col_nam);
4109 rrd_set_error("invalid color def format");
4118 old_locale = setlocale(LC_NUMERIC, "C");
4119 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4120 int sindex, propidx;
4122 setlocale(LC_NUMERIC, old_locale);
4123 if ((sindex = text_prop_conv(prop)) != -1) {
4124 for (propidx = sindex;
4125 propidx < TEXT_PROP_LAST; propidx++) {
4127 im->text_prop[propidx].size = size;
4129 if ((int) strlen(optarg) > end) {
4130 if (optarg[end] == ':') {
4131 strncpy(im->text_prop[propidx].font,
4132 optarg + end + 1, 255);
4133 im->text_prop[propidx].font[255] = '\0';
4136 ("expected : after font size in '%s'",
4141 /* only run the for loop for DEFAULT (0) for
4142 all others, we break here. woodo programming */
4143 if (propidx == sindex && sindex != 0)
4147 rrd_set_error("invalid fonttag '%s'", prop);
4151 setlocale(LC_NUMERIC, old_locale);
4152 rrd_set_error("invalid text property format");
4158 old_locale = setlocale(LC_NUMERIC, "C");
4159 im->zoom = atof(optarg);
4160 setlocale(LC_NUMERIC, old_locale);
4161 if (im->zoom <= 0.0) {
4162 rrd_set_error("zoom factor must be > 0");
4167 strncpy(im->title, optarg, 150);
4168 im->title[150] = '\0';
4171 if (strcmp(optarg, "normal") == 0) {
4172 cairo_font_options_set_antialias
4173 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4174 cairo_font_options_set_hint_style
4175 (im->font_options, CAIRO_HINT_STYLE_FULL);
4176 } else if (strcmp(optarg, "light") == 0) {
4177 cairo_font_options_set_antialias
4178 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4179 cairo_font_options_set_hint_style
4180 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4181 } else if (strcmp(optarg, "mono") == 0) {
4182 cairo_font_options_set_antialias
4183 (im->font_options, CAIRO_ANTIALIAS_NONE);
4184 cairo_font_options_set_hint_style
4185 (im->font_options, CAIRO_HINT_STYLE_FULL);
4187 rrd_set_error("unknown font-render-mode '%s'", optarg);
4192 if (strcmp(optarg, "normal") == 0)
4193 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4194 else if (strcmp(optarg, "mono") == 0)
4195 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4197 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4202 /* not supported curently */
4205 strncpy(im->watermark, optarg, 100);
4206 im->watermark[99] = '\0';
4210 rrd_set_error("unknown option '%c'", optopt);
4212 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4217 if (im->logarithmic && im->minval <= 0) {
4219 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4223 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4224 /* error string is set in parsetime.c */
4228 if (start_tmp < 3600 * 24 * 365 * 10) {
4230 ("the first entry to fetch should be after 1980 (%ld)",
4235 if (end_tmp < start_tmp) {
4237 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4241 im->start = start_tmp;
4243 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4246 int rrd_graph_color(
4254 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4256 color = strstr(var, "#");
4257 if (color == NULL) {
4258 if (optional == 0) {
4259 rrd_set_error("Found no color in %s", err);
4266 long unsigned int col;
4268 rest = strstr(color, ":");
4275 sscanf(color, "#%6lx%n", &col, &n);
4276 col = (col << 8) + 0xff /* shift left by 8 */ ;
4278 rrd_set_error("Color problem in %s", err);
4281 sscanf(color, "#%8lx%n", &col, &n);
4285 rrd_set_error("Color problem in %s", err);
4287 if (rrd_test_error())
4289 gdp->col = gfx_hex_to_col(col);
4302 while (*ptr != '\0')
4303 if (*ptr++ == '%') {
4305 /* line cannot end with percent char */
4308 /* '%s', '%S' and '%%' are allowed */
4309 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4311 /* %c is allowed (but use only with vdef!) */
4312 else if (*ptr == 'c') {
4317 /* or else '% 6.2lf' and such are allowed */
4319 /* optional padding character */
4320 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4322 /* This should take care of 'm.n' with all three optional */
4323 while (*ptr >= '0' && *ptr <= '9')
4327 while (*ptr >= '0' && *ptr <= '9')
4329 /* Either 'le', 'lf' or 'lg' must follow here */
4332 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4347 const char *const str)
4349 /* A VDEF currently is either "func" or "param,func"
4350 * so the parsing is rather simple. Change if needed.
4358 old_locale = setlocale(LC_NUMERIC, "C");
4359 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4360 setlocale(LC_NUMERIC, old_locale);
4361 if (n == (int) strlen(str)) { /* matched */
4365 sscanf(str, "%29[A-Z]%n", func, &n);
4366 if (n == (int) strlen(str)) { /* matched */
4370 ("Unknown function string '%s' in VDEF '%s'",
4375 if (!strcmp("PERCENT", func))
4376 gdes->vf.op = VDEF_PERCENT;
4377 else if (!strcmp("MAXIMUM", func))
4378 gdes->vf.op = VDEF_MAXIMUM;
4379 else if (!strcmp("AVERAGE", func))
4380 gdes->vf.op = VDEF_AVERAGE;
4381 else if (!strcmp("STDEV", func))
4382 gdes->vf.op = VDEF_STDEV;
4383 else if (!strcmp("MINIMUM", func))
4384 gdes->vf.op = VDEF_MINIMUM;
4385 else if (!strcmp("TOTAL", func))
4386 gdes->vf.op = VDEF_TOTAL;
4387 else if (!strcmp("FIRST", func))
4388 gdes->vf.op = VDEF_FIRST;
4389 else if (!strcmp("LAST", func))
4390 gdes->vf.op = VDEF_LAST;
4391 else if (!strcmp("LSLSLOPE", func))
4392 gdes->vf.op = VDEF_LSLSLOPE;
4393 else if (!strcmp("LSLINT", func))
4394 gdes->vf.op = VDEF_LSLINT;
4395 else if (!strcmp("LSLCORREL", func))
4396 gdes->vf.op = VDEF_LSLCORREL;
4399 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4402 switch (gdes->vf.op) {
4404 if (isnan(param)) { /* no parameter given */
4406 ("Function '%s' needs parameter in VDEF '%s'\n",
4410 if (param >= 0.0 && param <= 100.0) {
4411 gdes->vf.param = param;
4412 gdes->vf.val = DNAN; /* undefined */
4413 gdes->vf.when = 0; /* undefined */
4416 ("Parameter '%f' out of range in VDEF '%s'\n",
4417 param, gdes->vname);
4430 case VDEF_LSLCORREL:
4432 gdes->vf.param = DNAN;
4433 gdes->vf.val = DNAN;
4437 ("Function '%s' needs no parameter in VDEF '%s'\n",
4451 graph_desc_t *src, *dst;
4456 dst = &im->gdes[gdi];
4457 src = &im->gdes[dst->vidx];
4458 data = src->data + src->ds;
4460 src->end_orig % src->step ==
4461 0 ? src->end_orig : (src->end_orig + src->step -
4462 src->end_orig % src->step);
4464 steps = (end - src->start) / src->step;
4467 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4468 src->start, src->end_orig, steps);
4470 switch (dst->vf.op) {
4474 if ((array = malloc(steps * sizeof(double))) == NULL) {
4475 rrd_set_error("malloc VDEV_PERCENT");
4478 for (step = 0; step < steps; step++) {
4479 array[step] = data[step * src->ds_cnt];
4481 qsort(array, step, sizeof(double), vdef_percent_compar);
4482 field = (steps - 1) * dst->vf.param / 100;
4483 dst->vf.val = array[field];
4484 dst->vf.when = 0; /* no time component */
4487 for (step = 0; step < steps; step++)
4488 printf("DEBUG: %3li:%10.2f %c\n",
4489 step, array[step], step == field ? '*' : ' ');
4495 while (step != steps && isnan(data[step * src->ds_cnt]))
4497 if (step == steps) {
4501 dst->vf.val = data[step * src->ds_cnt];
4502 dst->vf.when = src->start + (step + 1) * src->step;
4504 while (step != steps) {
4505 if (finite(data[step * src->ds_cnt])) {
4506 if (data[step * src->ds_cnt] > dst->vf.val) {
4507 dst->vf.val = data[step * src->ds_cnt];
4508 dst->vf.when = src->start + (step + 1) * src->step;
4519 double average = 0.0;
4521 for (step = 0; step < steps; step++) {
4522 if (finite(data[step * src->ds_cnt])) {
4523 sum += data[step * src->ds_cnt];
4528 if (dst->vf.op == VDEF_TOTAL) {
4529 dst->vf.val = sum * src->step;
4530 dst->vf.when = 0; /* no time component */
4531 } else if (dst->vf.op == VDEF_AVERAGE) {
4532 dst->vf.val = sum / cnt;
4533 dst->vf.when = 0; /* no time component */
4535 average = sum / cnt;
4537 for (step = 0; step < steps; step++) {
4538 if (finite(data[step * src->ds_cnt])) {
4539 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4542 dst->vf.val = pow(sum / cnt, 0.5);
4543 dst->vf.when = 0; /* no time component */
4553 while (step != steps && isnan(data[step * src->ds_cnt]))
4555 if (step == steps) {
4559 dst->vf.val = data[step * src->ds_cnt];
4560 dst->vf.when = src->start + (step + 1) * src->step;
4562 while (step != steps) {
4563 if (finite(data[step * src->ds_cnt])) {
4564 if (data[step * src->ds_cnt] < dst->vf.val) {
4565 dst->vf.val = data[step * src->ds_cnt];
4566 dst->vf.when = src->start + (step + 1) * src->step;
4573 /* The time value returned here is one step before the
4574 * actual time value. This is the start of the first
4578 while (step != steps && isnan(data[step * src->ds_cnt]))
4580 if (step == steps) { /* all entries were NaN */
4584 dst->vf.val = data[step * src->ds_cnt];
4585 dst->vf.when = src->start + step * src->step;
4589 /* The time value returned here is the
4590 * actual time value. This is the end of the last
4594 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4596 if (step < 0) { /* all entries were NaN */
4600 dst->vf.val = data[step * src->ds_cnt];
4601 dst->vf.when = src->start + (step + 1) * src->step;
4606 case VDEF_LSLCORREL:{
4607 /* Bestfit line by linear least squares method */
4610 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4617 for (step = 0; step < steps; step++) {
4618 if (finite(data[step * src->ds_cnt])) {
4621 SUMxx += step * step;
4622 SUMxy += step * data[step * src->ds_cnt];
4623 SUMy += data[step * src->ds_cnt];
4624 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4628 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4629 y_intercept = (SUMy - slope * SUMx) / cnt;
4632 (SUMx * SUMy) / cnt) /
4634 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4636 if (dst->vf.op == VDEF_LSLSLOPE) {
4637 dst->vf.val = slope;
4639 } else if (dst->vf.op == VDEF_LSLINT) {
4640 dst->vf.val = y_intercept;
4642 } else if (dst->vf.op == VDEF_LSLCORREL) {
4643 dst->vf.val = correl;
4656 /* NaN < -INF < finite_values < INF */
4657 int vdef_percent_compar(
4663 /* Equality is not returned; this doesn't hurt except
4664 * (maybe) for a little performance.
4667 /* First catch NaN values. They are smallest */
4668 if (isnan(*(double *) a))
4670 if (isnan(*(double *) b))
4672 /* NaN doesn't reach this part so INF and -INF are extremes.
4673 * The sign from isinf() is compatible with the sign we return
4675 if (isinf(*(double *) a))
4676 return isinf(*(double *) a);
4677 if (isinf(*(double *) b))
4678 return isinf(*(double *) b);
4679 /* If we reach this, both values must be finite */
4680 if (*(double *) a < *(double *) b)
4689 enum info_type type,
4692 im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4693 if (im->grinfo == NULL) {
4694 im->grinfo = im->grinfo_current;