1 /****************************************************************************
2 * RRDtool 1.2.99907080300 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT}
42 {9.0, RRD_DEFAULT_FONT}
44 {7.0, RRD_DEFAULT_FONT}
46 {8.0, RRD_DEFAULT_FONT}
48 {8.0, RRD_DEFAULT_FONT} /* legend */
52 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
54 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
56 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
58 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
60 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
62 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
64 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
66 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
68 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
70 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
71 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
73 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
75 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
77 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
79 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
81 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
84 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
87 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
90 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
92 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93 365 * 24 * 3600, "%y"}
95 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
98 /* sensible y label intervals ...*/
122 {20.0, {1, 5, 10, 20}
128 {100.0, {1, 2, 5, 10}
131 {200.0, {1, 5, 10, 20}
134 {500.0, {1, 2, 4, 10}
142 gfx_color_t graph_col[] = /* default colors */
144 {1.00, 1.00, 1.00, 1.00}, /* canvas */
145 {0.95, 0.95, 0.95, 1.00}, /* background */
146 {0.81, 0.81, 0.81, 1.00}, /* shade A */
147 {0.62, 0.62, 0.62, 1.00}, /* shade B */
148 {0.56, 0.56, 0.56, 0.75}, /* grid */
149 {0.87, 0.31, 0.31, 0.60}, /* major grid */
150 {0.00, 0.00, 0.00, 1.00}, /* font */
151 {0.50, 0.12, 0.12, 1.00}, /* arrow */
152 {0.12, 0.12, 0.12, 1.00}, /* axis */
153 {0.00, 0.00, 0.00, 1.00} /* frame */
160 # define DPRINT(x) (void)(printf x, printf("\n"))
166 /* initialize with xtr(im,0); */
174 pixie = (double) im->xsize / (double) (im->end - im->start);
177 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
180 /* translate data values into y coordinates */
189 if (!im->logarithmic)
190 pixie = (double) im->ysize / (im->maxval - im->minval);
193 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
195 } else if (!im->logarithmic) {
196 yval = im->yorigin - pixie * (value - im->minval);
198 if (value < im->minval) {
201 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
204 /* make sure we don't return anything too unreasonable. GD lib can
205 get terribly slow when drawing lines outside its scope. This is
206 especially problematic in connection with the rigid option */
208 /* keep yval as-is */
209 } else if (yval > im->yorigin) {
210 yval = im->yorigin + 0.00001;
211 } else if (yval < im->yorigin - im->ysize) {
212 yval = im->yorigin - im->ysize - 0.00001;
219 /* conversion function for symbolic entry names */
222 #define conv_if(VV,VVV) \
223 if (strcmp(#VV, string) == 0) return VVV ;
229 conv_if(PRINT, GF_PRINT);
230 conv_if(GPRINT, GF_GPRINT);
231 conv_if(COMMENT, GF_COMMENT);
232 conv_if(HRULE, GF_HRULE);
233 conv_if(VRULE, GF_VRULE);
234 conv_if(LINE, GF_LINE);
235 conv_if(AREA, GF_AREA);
236 conv_if(STACK, GF_STACK);
237 conv_if(TICK, GF_TICK);
238 conv_if(TEXTALIGN, GF_TEXTALIGN);
239 conv_if(DEF, GF_DEF);
240 conv_if(CDEF, GF_CDEF);
241 conv_if(VDEF, GF_VDEF);
242 conv_if(XPORT, GF_XPORT);
243 conv_if(SHIFT, GF_SHIFT);
248 enum gfx_if_en if_conv(
252 conv_if(PNG, IF_PNG);
253 conv_if(SVG, IF_SVG);
254 conv_if(EPS, IF_EPS);
255 conv_if(PDF, IF_PDF);
260 enum tmt_en tmt_conv(
264 conv_if(SECOND, TMT_SECOND);
265 conv_if(MINUTE, TMT_MINUTE);
266 conv_if(HOUR, TMT_HOUR);
267 conv_if(DAY, TMT_DAY);
268 conv_if(WEEK, TMT_WEEK);
269 conv_if(MONTH, TMT_MONTH);
270 conv_if(YEAR, TMT_YEAR);
274 enum grc_en grc_conv(
278 conv_if(BACK, GRC_BACK);
279 conv_if(CANVAS, GRC_CANVAS);
280 conv_if(SHADEA, GRC_SHADEA);
281 conv_if(SHADEB, GRC_SHADEB);
282 conv_if(GRID, GRC_GRID);
283 conv_if(MGRID, GRC_MGRID);
284 conv_if(FONT, GRC_FONT);
285 conv_if(ARROW, GRC_ARROW);
286 conv_if(AXIS, GRC_AXIS);
287 conv_if(FRAME, GRC_FRAME);
292 enum text_prop_en text_prop_conv(
296 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
297 conv_if(TITLE, TEXT_PROP_TITLE);
298 conv_if(AXIS, TEXT_PROP_AXIS);
299 conv_if(UNIT, TEXT_PROP_UNIT);
300 conv_if(LEGEND, TEXT_PROP_LEGEND);
311 cairo_status_t status = 0;
315 for (i = 0; i < (unsigned) im->gdes_c; i++) {
316 if (im->gdes[i].data_first) {
317 /* careful here, because a single pointer can occur several times */
318 free(im->gdes[i].data);
319 if (im->gdes[i].ds_namv) {
320 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321 free(im->gdes[i].ds_namv[ii]);
322 free(im->gdes[i].ds_namv);
325 /* free allocated memory used for dashed lines */
326 if (im->gdes[i].p_dashes != NULL)
327 free(im->gdes[i].p_dashes);
329 free(im->gdes[i].p_data);
330 free(im->gdes[i].rpnp);
333 if (im->font_options)
334 cairo_font_options_destroy(im->font_options);
337 status = cairo_status(im->cr);
338 cairo_destroy(im->cr);
340 if (im->rendered_image) {
341 free(im->rendered_image);
344 cairo_surface_destroy(im->surface);
346 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
347 cairo_status_to_string(status));
352 /* find SI magnitude symbol for the given number*/
354 image_desc_t *im, /* image description */
360 char *symbol[] = { "a", /* 10e-18 Atto */
361 "f", /* 10e-15 Femto */
362 "p", /* 10e-12 Pico */
363 "n", /* 10e-9 Nano */
364 "u", /* 10e-6 Micro */
365 "m", /* 10e-3 Milli */
370 "T", /* 10e12 Tera */
371 "P", /* 10e15 Peta */
378 if (*value == 0.0 || isnan(*value)) {
382 sindex = floor(log(fabs(*value)) / log((double) im->base));
383 *magfact = pow((double) im->base, (double) sindex);
384 (*value) /= (*magfact);
386 if (sindex <= symbcenter && sindex >= -symbcenter) {
387 (*symb_ptr) = symbol[sindex + symbcenter];
394 static char si_symbol[] = {
395 'a', /* 10e-18 Atto */
396 'f', /* 10e-15 Femto */
397 'p', /* 10e-12 Pico */
398 'n', /* 10e-9 Nano */
399 'u', /* 10e-6 Micro */
400 'm', /* 10e-3 Milli */
405 'T', /* 10e12 Tera */
406 'P', /* 10e15 Peta */
409 static const int si_symbcenter = 6;
411 /* find SI magnitude symbol for the numbers on the y-axis*/
413 image_desc_t *im /* image description */
417 double digits, viewdigits = 0;
420 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
421 log((double) im->base));
423 if (im->unitsexponent != 9999) {
424 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
425 viewdigits = floor(im->unitsexponent / 3);
430 im->magfact = pow((double) im->base, digits);
433 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
436 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
438 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
439 ((viewdigits + si_symbcenter) >= 0))
440 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
445 /* move min and max values around to become sensible */
450 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
451 600.0, 500.0, 400.0, 300.0, 250.0,
452 200.0, 125.0, 100.0, 90.0, 80.0,
453 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
454 25.0, 20.0, 10.0, 9.0, 8.0,
455 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
456 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
457 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
460 double scaled_min, scaled_max;
467 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
468 im->minval, im->maxval, im->magfact);
471 if (isnan(im->ygridstep)) {
472 if (im->extra_flags & ALTAUTOSCALE) {
473 /* measure the amplitude of the function. Make sure that
474 graph boundaries are slightly higher then max/min vals
475 so we can see amplitude on the graph */
478 delt = im->maxval - im->minval;
480 fact = 2.0 * pow(10.0,
482 (max(fabs(im->minval), fabs(im->maxval)) /
485 adj = (fact - delt) * 0.55;
488 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
489 im->minval, im->maxval, delt, fact, adj);
494 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
495 /* measure the amplitude of the function. Make sure that
496 graph boundaries are slightly lower than min vals
497 so we can see amplitude on the graph */
498 adj = (im->maxval - im->minval) * 0.1;
500 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
501 /* measure the amplitude of the function. Make sure that
502 graph boundaries are slightly higher than max vals
503 so we can see amplitude on the graph */
504 adj = (im->maxval - im->minval) * 0.1;
507 scaled_min = im->minval / im->magfact;
508 scaled_max = im->maxval / im->magfact;
510 for (i = 1; sensiblevalues[i] > 0; i++) {
511 if (sensiblevalues[i - 1] >= scaled_min &&
512 sensiblevalues[i] <= scaled_min)
513 im->minval = sensiblevalues[i] * (im->magfact);
515 if (-sensiblevalues[i - 1] <= scaled_min &&
516 -sensiblevalues[i] >= scaled_min)
517 im->minval = -sensiblevalues[i - 1] * (im->magfact);
519 if (sensiblevalues[i - 1] >= scaled_max &&
520 sensiblevalues[i] <= scaled_max)
521 im->maxval = sensiblevalues[i - 1] * (im->magfact);
523 if (-sensiblevalues[i - 1] <= scaled_max &&
524 -sensiblevalues[i] >= scaled_max)
525 im->maxval = -sensiblevalues[i] * (im->magfact);
529 /* adjust min and max to the grid definition if there is one */
530 im->minval = (double) im->ylabfact * im->ygridstep *
531 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
532 im->maxval = (double) im->ylabfact * im->ygridstep *
533 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
537 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
538 im->minval, im->maxval, im->magfact);
546 if (isnan(im->minval) || isnan(im->maxval))
549 if (im->logarithmic) {
550 double ya, yb, ypix, ypixfrac;
551 double log10_range = log10(im->maxval) - log10(im->minval);
553 ya = pow((double) 10, floor(log10(im->minval)));
554 while (ya < im->minval)
557 return; /* don't have y=10^x gridline */
559 if (yb <= im->maxval) {
560 /* we have at least 2 y=10^x gridlines.
561 Make sure distance between them in pixels
562 are an integer by expanding im->maxval */
563 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
564 double factor = y_pixel_delta / floor(y_pixel_delta);
565 double new_log10_range = factor * log10_range;
566 double new_ymax_log10 = log10(im->minval) + new_log10_range;
568 im->maxval = pow(10, new_ymax_log10);
569 ytr(im, DNAN); /* reset precalc */
570 log10_range = log10(im->maxval) - log10(im->minval);
572 /* make sure first y=10^x gridline is located on
573 integer pixel position by moving scale slightly
574 downwards (sub-pixel movement) */
575 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
576 ypixfrac = ypix - floor(ypix);
577 if (ypixfrac > 0 && ypixfrac < 1) {
578 double yfrac = ypixfrac / im->ysize;
580 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
581 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
582 ytr(im, DNAN); /* reset precalc */
585 /* Make sure we have an integer pixel distance between
586 each minor gridline */
587 double ypos1 = ytr(im, im->minval);
588 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
589 double y_pixel_delta = ypos1 - ypos2;
590 double factor = y_pixel_delta / floor(y_pixel_delta);
591 double new_range = factor * (im->maxval - im->minval);
592 double gridstep = im->ygrid_scale.gridstep;
593 double minor_y, minor_y_px, minor_y_px_frac;
595 if (im->maxval > 0.0)
596 im->maxval = im->minval + new_range;
598 im->minval = im->maxval - new_range;
599 ytr(im, DNAN); /* reset precalc */
600 /* make sure first minor gridline is on integer pixel y coord */
601 minor_y = gridstep * floor(im->minval / gridstep);
602 while (minor_y < im->minval)
604 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
605 minor_y_px_frac = minor_y_px - floor(minor_y_px);
606 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
607 double yfrac = minor_y_px_frac / im->ysize;
608 double range = im->maxval - im->minval;
610 im->minval = im->minval - yfrac * range;
611 im->maxval = im->maxval - yfrac * range;
612 ytr(im, DNAN); /* reset precalc */
614 calc_horizontal_grid(im); /* recalc with changed im->maxval */
618 /* reduce data reimplementation by Alex */
621 enum cf_en cf, /* which consolidation function ? */
622 unsigned long cur_step, /* step the data currently is in */
623 time_t *start, /* start, end and step as requested ... */
624 time_t *end, /* ... by the application will be ... */
625 unsigned long *step, /* ... adjusted to represent reality */
626 unsigned long *ds_cnt, /* number of data sources in file */
628 { /* two dimensional array containing the data */
629 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
630 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
632 rrd_value_t *srcptr, *dstptr;
634 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
637 row_cnt = ((*end) - (*start)) / cur_step;
643 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
644 row_cnt, reduce_factor, *start, *end, cur_step);
645 for (col = 0; col < row_cnt; col++) {
646 printf("time %10lu: ", *start + (col + 1) * cur_step);
647 for (i = 0; i < *ds_cnt; i++)
648 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
653 /* We have to combine [reduce_factor] rows of the source
654 ** into one row for the destination. Doing this we also
655 ** need to take care to combine the correct rows. First
656 ** alter the start and end time so that they are multiples
657 ** of the new step time. We cannot reduce the amount of
658 ** time so we have to move the end towards the future and
659 ** the start towards the past.
661 end_offset = (*end) % (*step);
662 start_offset = (*start) % (*step);
664 /* If there is a start offset (which cannot be more than
665 ** one destination row), skip the appropriate number of
666 ** source rows and one destination row. The appropriate
667 ** number is what we do know (start_offset/cur_step) of
668 ** the new interval (*step/cur_step aka reduce_factor).
671 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
672 printf("row_cnt before: %lu\n", row_cnt);
675 (*start) = (*start) - start_offset;
676 skiprows = reduce_factor - start_offset / cur_step;
677 srcptr += skiprows * *ds_cnt;
678 for (col = 0; col < (*ds_cnt); col++)
683 printf("row_cnt between: %lu\n", row_cnt);
686 /* At the end we have some rows that are not going to be
687 ** used, the amount is end_offset/cur_step
690 (*end) = (*end) - end_offset + (*step);
691 skiprows = end_offset / cur_step;
695 printf("row_cnt after: %lu\n", row_cnt);
698 /* Sanity check: row_cnt should be multiple of reduce_factor */
699 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
701 if (row_cnt % reduce_factor) {
702 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
703 row_cnt, reduce_factor);
704 printf("BUG in reduce_data()\n");
708 /* Now combine reduce_factor intervals at a time
709 ** into one interval for the destination.
712 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
713 for (col = 0; col < (*ds_cnt); col++) {
714 rrd_value_t newval = DNAN;
715 unsigned long validval = 0;
717 for (i = 0; i < reduce_factor; i++) {
718 if (isnan(srcptr[i * (*ds_cnt) + col])) {
723 newval = srcptr[i * (*ds_cnt) + col];
732 newval += srcptr[i * (*ds_cnt) + col];
735 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
738 /* an interval contains a failure if any subintervals contained a failure */
740 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
743 newval = srcptr[i * (*ds_cnt) + col];
769 srcptr += (*ds_cnt) * reduce_factor;
770 row_cnt -= reduce_factor;
772 /* If we had to alter the endtime, we didn't have enough
773 ** source rows to fill the last row. Fill it with NaN.
776 for (col = 0; col < (*ds_cnt); col++)
779 row_cnt = ((*end) - (*start)) / *step;
781 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
782 row_cnt, *start, *end, *step);
783 for (col = 0; col < row_cnt; col++) {
784 printf("time %10lu: ", *start + (col + 1) * (*step));
785 for (i = 0; i < *ds_cnt; i++)
786 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
793 /* get the data required for the graphs from the
802 /* pull the data from the rrd files ... */
803 for (i = 0; i < (int) im->gdes_c; i++) {
804 /* only GF_DEF elements fetch data */
805 if (im->gdes[i].gf != GF_DEF)
809 /* do we have it already ? */
810 for (ii = 0; ii < i; ii++) {
811 if (im->gdes[ii].gf != GF_DEF)
813 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
814 && (im->gdes[i].cf == im->gdes[ii].cf)
815 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
816 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
817 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
818 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
819 /* OK, the data is already there.
820 ** Just copy the header portion
822 im->gdes[i].start = im->gdes[ii].start;
823 im->gdes[i].end = im->gdes[ii].end;
824 im->gdes[i].step = im->gdes[ii].step;
825 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
826 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
827 im->gdes[i].data = im->gdes[ii].data;
828 im->gdes[i].data_first = 0;
835 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
837 if ((rrd_fetch_fn(im->gdes[i].rrd,
843 &im->gdes[i].ds_namv,
844 &im->gdes[i].data)) == -1) {
847 im->gdes[i].data_first = 1;
849 if (ft_step < im->gdes[i].step) {
850 reduce_data(im->gdes[i].cf_reduce,
855 &im->gdes[i].ds_cnt, &im->gdes[i].data);
857 im->gdes[i].step = ft_step;
861 /* lets see if the required data source is really there */
862 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
863 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
867 if (im->gdes[i].ds == -1) {
868 rrd_set_error("No DS called '%s' in '%s'",
869 im->gdes[i].ds_nam, im->gdes[i].rrd);
877 /* evaluate the expressions in the CDEF functions */
879 /*************************************************************
881 *************************************************************/
883 long find_var_wrapper(
887 return find_var((image_desc_t *) arg1, key);
890 /* find gdes containing var*/
897 for (ii = 0; ii < im->gdes_c - 1; ii++) {
898 if ((im->gdes[ii].gf == GF_DEF
899 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
900 && (strcmp(im->gdes[ii].vname, key) == 0)) {
907 /* find the largest common denominator for all the numbers
908 in the 0 terminated num array */
915 for (i = 0; num[i + 1] != 0; i++) {
917 rest = num[i] % num[i + 1];
923 /* return i==0?num[i]:num[i-1]; */
927 /* run the rpn calculator on all the VDEF and CDEF arguments */
934 long *steparray, rpi;
939 rpnstack_init(&rpnstack);
941 for (gdi = 0; gdi < im->gdes_c; gdi++) {
942 /* Look for GF_VDEF and GF_CDEF in the same loop,
943 * so CDEFs can use VDEFs and vice versa
945 switch (im->gdes[gdi].gf) {
949 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
951 /* remove current shift */
952 vdp->start -= vdp->shift;
953 vdp->end -= vdp->shift;
956 if (im->gdes[gdi].shidx >= 0)
957 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
960 vdp->shift = im->gdes[gdi].shval;
962 /* normalize shift to multiple of consolidated step */
963 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
966 vdp->start += vdp->shift;
967 vdp->end += vdp->shift;
971 /* A VDEF has no DS. This also signals other parts
972 * of rrdtool that this is a VDEF value, not a CDEF.
974 im->gdes[gdi].ds_cnt = 0;
975 if (vdef_calc(im, gdi)) {
976 rrd_set_error("Error processing VDEF '%s'",
977 im->gdes[gdi].vname);
978 rpnstack_free(&rpnstack);
983 im->gdes[gdi].ds_cnt = 1;
984 im->gdes[gdi].ds = 0;
985 im->gdes[gdi].data_first = 1;
986 im->gdes[gdi].start = 0;
987 im->gdes[gdi].end = 0;
992 /* Find the variables in the expression.
993 * - VDEF variables are substituted by their values
994 * and the opcode is changed into OP_NUMBER.
995 * - CDEF variables are analized for their step size,
996 * the lowest common denominator of all the step
997 * sizes of the data sources involved is calculated
998 * and the resulting number is the step size for the
999 * resulting data source.
1001 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1002 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1003 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1004 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1006 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1009 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1010 im->gdes[gdi].vname, im->gdes[ptr].vname);
1011 printf("DEBUG: value from vdef is %f\n",
1012 im->gdes[ptr].vf.val);
1014 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1015 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1016 } else { /* normal variables and PREF(variables) */
1018 /* add one entry to the array that keeps track of the step sizes of the
1019 * data sources going into the CDEF. */
1021 rrd_realloc(steparray,
1023 1) * sizeof(*steparray))) == NULL) {
1024 rrd_set_error("realloc steparray");
1025 rpnstack_free(&rpnstack);
1029 steparray[stepcnt - 1] = im->gdes[ptr].step;
1031 /* adjust start and end of cdef (gdi) so
1032 * that it runs from the latest start point
1033 * to the earliest endpoint of any of the
1034 * rras involved (ptr)
1037 if (im->gdes[gdi].start < im->gdes[ptr].start)
1038 im->gdes[gdi].start = im->gdes[ptr].start;
1040 if (im->gdes[gdi].end == 0 ||
1041 im->gdes[gdi].end > im->gdes[ptr].end)
1042 im->gdes[gdi].end = im->gdes[ptr].end;
1044 /* store pointer to the first element of
1045 * the rra providing data for variable,
1046 * further save step size and data source
1049 im->gdes[gdi].rpnp[rpi].data =
1050 im->gdes[ptr].data + im->gdes[ptr].ds;
1051 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1052 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1054 /* backoff the *.data ptr; this is done so
1055 * rpncalc() function doesn't have to treat
1056 * the first case differently
1058 } /* if ds_cnt != 0 */
1059 } /* if OP_VARIABLE */
1060 } /* loop through all rpi */
1062 /* move the data pointers to the correct period */
1063 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1064 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1065 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1066 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1068 im->gdes[gdi].start - im->gdes[ptr].start;
1071 im->gdes[gdi].rpnp[rpi].data +=
1072 (diff / im->gdes[ptr].step) *
1073 im->gdes[ptr].ds_cnt;
1077 if (steparray == NULL) {
1078 rrd_set_error("rpn expressions without DEF"
1079 " or CDEF variables are not supported");
1080 rpnstack_free(&rpnstack);
1083 steparray[stepcnt] = 0;
1084 /* Now find the resulting step. All steps in all
1085 * used RRAs have to be visited
1087 im->gdes[gdi].step = lcd(steparray);
1089 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1090 im->gdes[gdi].start)
1091 / im->gdes[gdi].step)
1092 * sizeof(double))) == NULL) {
1093 rrd_set_error("malloc im->gdes[gdi].data");
1094 rpnstack_free(&rpnstack);
1098 /* Step through the new cdef results array and
1099 * calculate the values
1101 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1102 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1103 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1105 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1106 * in this case we are advancing by timesteps;
1107 * we use the fact that time_t is a synonym for long
1109 if (rpn_calc(rpnp, &rpnstack, (long) now,
1110 im->gdes[gdi].data, ++dataidx) == -1) {
1111 /* rpn_calc sets the error string */
1112 rpnstack_free(&rpnstack);
1115 } /* enumerate over time steps within a CDEF */
1120 } /* enumerate over CDEFs */
1121 rpnstack_free(&rpnstack);
1125 static int AlmostEqual2sComplement(
1131 int aInt = *(int *) &A;
1132 int bInt = *(int *) &B;
1135 /* Make sure maxUlps is non-negative and small enough that the
1136 default NAN won't compare as equal to anything. */
1138 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1140 /* Make aInt lexicographically ordered as a twos-complement int */
1143 aInt = 0x80000000l - aInt;
1145 /* Make bInt lexicographically ordered as a twos-complement int */
1148 bInt = 0x80000000l - bInt;
1150 intDiff = abs(aInt - bInt);
1152 if (intDiff <= maxUlps)
1158 /* massage data so, that we get one value for each x coordinate in the graph */
1163 double pixstep = (double) (im->end - im->start)
1164 / (double) im->xsize; /* how much time
1165 passes in one pixel */
1167 double minval = DNAN, maxval = DNAN;
1169 unsigned long gr_time;
1171 /* memory for the processed data */
1172 for (i = 0; i < im->gdes_c; i++) {
1173 if ((im->gdes[i].gf == GF_LINE) ||
1174 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1175 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1176 * sizeof(rrd_value_t))) == NULL) {
1177 rrd_set_error("malloc data_proc");
1183 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1186 gr_time = im->start + pixstep * i; /* time of the current step */
1189 for (ii = 0; ii < im->gdes_c; ii++) {
1192 switch (im->gdes[ii].gf) {
1196 if (!im->gdes[ii].stack)
1198 value = im->gdes[ii].yrule;
1199 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1200 /* The time of the data doesn't necessarily match
1201 ** the time of the graph. Beware.
1203 vidx = im->gdes[ii].vidx;
1204 if (im->gdes[vidx].gf == GF_VDEF) {
1205 value = im->gdes[vidx].vf.val;
1207 if (((long int) gr_time >=
1208 (long int) im->gdes[vidx].start)
1209 && ((long int) gr_time <=
1210 (long int) im->gdes[vidx].end)) {
1211 value = im->gdes[vidx].data[(unsigned long)
1217 im->gdes[vidx].step)
1218 * im->gdes[vidx].ds_cnt +
1225 if (!isnan(value)) {
1227 im->gdes[ii].p_data[i] = paintval;
1228 /* GF_TICK: the data values are not
1229 ** relevant for min and max
1231 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1232 if ((isnan(minval) || paintval < minval) &&
1233 !(im->logarithmic && paintval <= 0.0))
1235 if (isnan(maxval) || paintval > maxval)
1239 im->gdes[ii].p_data[i] = DNAN;
1244 ("STACK should already be turned into LINE or AREA here");
1253 /* if min or max have not been asigned a value this is because
1254 there was no data in the graph ... this is not good ...
1255 lets set these to dummy values then ... */
1257 if (im->logarithmic) {
1269 /* adjust min and max values */
1270 /* for logscale we add something on top */
1271 if (isnan(im->minval)
1272 || ((!im->rigid) && im->minval > minval)
1274 if (im->logarithmic)
1275 im->minval = minval * 0.5;
1277 im->minval = minval;
1279 if (isnan(im->maxval)
1280 || (!im->rigid && im->maxval < maxval)
1282 if (im->logarithmic)
1283 im->maxval = maxval * 2.0;
1285 im->maxval = maxval;
1288 /* make sure min is smaller than max */
1289 if (im->minval > im->maxval) {
1291 im->minval = 0.99 * im->maxval;
1293 im->minval = 1.01 * im->maxval;
1296 /* make sure min and max are not equal */
1297 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1303 /* make sure min and max are not both zero */
1304 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1313 /* identify the point where the first gridline, label ... gets placed */
1315 time_t find_first_time(
1316 time_t start, /* what is the initial time */
1317 enum tmt_en baseint, /* what is the basic interval */
1318 long basestep /* how many if these do we jump a time */
1323 localtime_r(&start, &tm);
1327 tm. tm_sec -= tm.tm_sec % basestep;
1332 tm. tm_min -= tm.tm_min % basestep;
1338 tm. tm_hour -= tm.tm_hour % basestep;
1342 /* we do NOT look at the basestep for this ... */
1349 /* we do NOT look at the basestep for this ... */
1353 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1355 if (tm.tm_wday == 0)
1356 tm. tm_mday -= 7; /* we want the *previous* monday */
1364 tm. tm_mon -= tm.tm_mon % basestep;
1375 tm.tm_year + 1900) %basestep;
1381 /* identify the point where the next gridline, label ... gets placed */
1382 time_t find_next_time(
1383 time_t current, /* what is the initial time */
1384 enum tmt_en baseint, /* what is the basic interval */
1385 long basestep /* how many if these do we jump a time */
1391 localtime_r(¤t, &tm);
1396 tm. tm_sec += basestep;
1400 tm. tm_min += basestep;
1404 tm. tm_hour += basestep;
1408 tm. tm_mday += basestep;
1412 tm. tm_mday += 7 * basestep;
1416 tm. tm_mon += basestep;
1420 tm. tm_year += basestep;
1422 madetime = mktime(&tm);
1423 } while (madetime == -1); /* this is necessary to skip impssible times
1424 like the daylight saving time skips */
1430 /* calculate values required for PRINT and GPRINT functions */
1435 long i, ii, validsteps;
1438 int graphelement = 0;
1441 double magfact = -1;
1446 /* wow initializing tmvdef is quite a task :-) */
1447 time_t now = time(NULL);
1449 localtime_r(&now, &tmvdef);
1450 for (i = 0; i < im->gdes_c; i++) {
1451 vidx = im->gdes[i].vidx;
1452 switch (im->gdes[i].gf) {
1455 /* PRINT and GPRINT can now print VDEF generated values.
1456 * There's no need to do any calculations on them as these
1457 * calculations were already made.
1459 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1460 printval = im->gdes[vidx].vf.val;
1461 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1462 } else { /* need to calculate max,min,avg etcetera */
1463 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1464 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1467 for (ii = im->gdes[vidx].ds;
1468 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1469 if (!finite(im->gdes[vidx].data[ii]))
1471 if (isnan(printval)) {
1472 printval = im->gdes[vidx].data[ii];
1477 switch (im->gdes[i].cf) {
1481 case CF_DEVSEASONAL:
1485 printval += im->gdes[vidx].data[ii];
1488 printval = min(printval, im->gdes[vidx].data[ii]);
1492 printval = max(printval, im->gdes[vidx].data[ii]);
1495 printval = im->gdes[vidx].data[ii];
1498 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1499 if (validsteps > 1) {
1500 printval = (printval / validsteps);
1503 } /* prepare printval */
1505 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1506 /* Magfact is set to -1 upon entry to print_calc. If it
1507 * is still less than 0, then we need to run auto_scale.
1508 * Otherwise, put the value into the correct units. If
1509 * the value is 0, then do not set the symbol or magnification
1510 * so next the calculation will be performed again. */
1511 if (magfact < 0.0) {
1512 auto_scale(im, &printval, &si_symb, &magfact);
1513 if (printval == 0.0)
1516 printval /= magfact;
1518 *(++percent_s) = 's';
1519 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1520 auto_scale(im, &printval, &si_symb, &magfact);
1523 if (im->gdes[i].gf == GF_PRINT) {
1526 if (im->gdes[i].strftm) {
1527 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1528 strftime(prline.u_str,
1529 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1530 } else if (bad_format(im->gdes[i].format)) {
1532 ("bad format for PRINT in '%s'", im->gdes[i].format);
1536 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1540 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1545 if (im->gdes[i].strftm) {
1546 strftime(im->gdes[i].legend,
1547 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1549 if (bad_format(im->gdes[i].format)) {
1551 ("bad format for GPRINT in '%s'",
1552 im->gdes[i].format);
1555 #ifdef HAVE_SNPRINTF
1556 snprintf(im->gdes[i].legend,
1558 im->gdes[i].format, printval, si_symb);
1560 sprintf(im->gdes[i].legend,
1561 im->gdes[i].format, printval, si_symb);
1573 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1574 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1579 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1580 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1589 #ifdef WITH_PIECHART
1597 ("STACK should already be turned into LINE or AREA here");
1602 return graphelement;
1606 /* place legends with color spots */
1612 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1613 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1614 int fill = 0, fill_last;
1616 int leg_x = border, 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 */
1625 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1626 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1627 rrd_set_error("malloc for legspace");
1631 if (im->extra_flags & FULL_SIZE_MODE)
1632 leg_y = leg_y_prev =
1633 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1634 for (i = 0; i < im->gdes_c; i++) {
1636 /* hide legends for rules which are not displayed */
1637 if (im->gdes[i].gf == GF_TEXTALIGN) {
1638 default_txtalign = im->gdes[i].txtalign;
1641 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1642 if (im->gdes[i].gf == GF_HRULE
1643 && (im->gdes[i].yrule <
1644 im->minval || im->gdes[i].yrule > im->maxval))
1645 im->gdes[i].legend[0] = '\0';
1646 if (im->gdes[i].gf == GF_VRULE
1647 && (im->gdes[i].xrule <
1648 im->start || im->gdes[i].xrule > im->end))
1649 im->gdes[i].legend[0] = '\0';
1652 leg_cc = strlen(im->gdes[i].legend);
1653 /* is there a controle code ant the end of the legend string ? */
1654 /* and it is not a tab \\t */
1656 && im->gdes[i].legend[leg_cc -
1658 && im->gdes[i].legend[leg_cc - 1] != 't') {
1659 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1661 im->gdes[i].legend[leg_cc] = '\0';
1665 /* only valid control codes */
1666 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1671 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1674 ("Unknown control code at the end of '%s\\%c'",
1675 im->gdes[i].legend, prt_fctn);
1679 if (prt_fctn == 'n') {
1683 /* remove exess space from the end of the legend for \g */
1684 while (prt_fctn == 'g' &&
1685 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1687 im->gdes[i].legend[leg_cc] = '\0';
1692 /* no interleg space if string ends in \g */
1693 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1695 fill += legspace[i];
1698 gfx_get_text_width(im,
1708 im->tabwidth, im->gdes[i].legend);
1713 /* who said there was a special tag ... ? */
1714 if (prt_fctn == 'g') {
1718 if (prt_fctn == '\0') {
1719 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1720 /* just one legend item is left right or center */
1721 switch (default_txtalign) {
1736 /* is it time to place the legends ? */
1737 if (fill > im->ximg - 2 * border) {
1745 if (leg_c == 1 && prt_fctn == 'j') {
1751 if (prt_fctn != '\0') {
1753 if (leg_c >= 2 && prt_fctn == 'j') {
1754 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1758 if (prt_fctn == 'c')
1759 leg_x = (im->ximg - fill) / 2.0;
1760 if (prt_fctn == 'r')
1761 leg_x = im->ximg - fill - border;
1762 for (ii = mark; ii <= i; ii++) {
1763 if (im->gdes[ii].legend[0] == '\0')
1764 continue; /* skip empty legends */
1765 im->gdes[ii].leg_x = leg_x;
1766 im->gdes[ii].leg_y = leg_y;
1768 gfx_get_text_width(im, leg_x,
1777 im->tabwidth, im->gdes[ii].legend)
1782 if (im->extra_flags & FULL_SIZE_MODE) {
1783 /* only add y space if there was text on the line */
1784 if (leg_x > border || prt_fctn == 's')
1785 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1786 if (prt_fctn == 's')
1787 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1789 if (leg_x > border || prt_fctn == 's')
1790 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1791 if (prt_fctn == 's')
1792 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1800 if (im->extra_flags & FULL_SIZE_MODE) {
1801 if (leg_y != leg_y_prev) {
1802 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1804 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1807 im->yimg = leg_y_prev;
1808 /* if we did place some legends we have to add vertical space */
1809 if (leg_y != im->yimg)
1810 im->yimg += 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 */
1825 int calc_horizontal_grid(
1833 int decimals, fractionals;
1835 im->ygrid_scale.labfact = 2;
1836 range = im->maxval - im->minval;
1837 scaledrange = range / im->magfact;
1838 /* does the scale of this graph make it impossible to put lines
1839 on it? If so, give up. */
1840 if (isnan(scaledrange)) {
1844 /* find grid spaceing */
1846 if (isnan(im->ygridstep)) {
1847 if (im->extra_flags & ALTYGRID) {
1848 /* find the value with max number of digits. Get number of digits */
1851 (max(fabs(im->maxval), fabs(im->minval)) *
1852 im->viewfactor / im->magfact));
1853 if (decimals <= 0) /* everything is small. make place for zero */
1855 im->ygrid_scale.gridstep =
1857 floor(log10(range * im->viewfactor / im->magfact))) /
1858 im->viewfactor * im->magfact;
1859 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1860 im->ygrid_scale.gridstep = 0.1;
1861 /* should have at least 5 lines but no more then 15 */
1862 if (range / im->ygrid_scale.gridstep < 5)
1863 im->ygrid_scale.gridstep /= 10;
1864 if (range / im->ygrid_scale.gridstep > 15)
1865 im->ygrid_scale.gridstep *= 10;
1866 if (range / im->ygrid_scale.gridstep > 5) {
1867 im->ygrid_scale.labfact = 1;
1868 if (range / im->ygrid_scale.gridstep > 8)
1869 im->ygrid_scale.labfact = 2;
1871 im->ygrid_scale.gridstep /= 5;
1872 im->ygrid_scale.labfact = 5;
1876 (im->ygrid_scale.gridstep *
1877 (double) im->ygrid_scale.labfact * im->viewfactor /
1879 if (fractionals < 0) { /* small amplitude. */
1880 int len = decimals - fractionals + 1;
1882 if (im->unitslength < len + 2)
1883 im->unitslength = len + 2;
1884 sprintf(im->ygrid_scale.labfmt,
1886 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1888 int len = decimals + 1;
1890 if (im->unitslength < len + 2)
1891 im->unitslength = len + 2;
1892 sprintf(im->ygrid_scale.labfmt,
1893 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1896 for (i = 0; ylab[i].grid > 0; i++) {
1897 pixel = im->ysize / (scaledrange / ylab[i].grid);
1903 for (i = 0; i < 4; i++) {
1904 if (pixel * ylab[gridind].lfac[i] >=
1905 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1906 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1911 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1914 im->ygrid_scale.gridstep = im->ygridstep;
1915 im->ygrid_scale.labfact = im->ylabfact;
1920 int draw_horizontal_grid(
1926 char graph_label[100];
1928 double X0 = im->xorigin;
1929 double X1 = im->xorigin + im->xsize;
1930 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1931 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1935 im->ygrid_scale.gridstep /
1936 (double) im->magfact * (double) im->viewfactor;
1937 MaxY = scaledstep * (double) egrid;
1938 for (i = sgrid; i <= egrid; i++) {
1940 im->ygrid_scale.gridstep * i);
1942 im->ygrid_scale.gridstep * (i + 1));
1944 if (floor(Y0 + 0.5) >=
1945 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1946 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1947 with the chosen settings. Add a label if required by settings, or if
1948 there is only one label so far and the next grid line is out of bounds. */
1949 if (i % im->ygrid_scale.labfact == 0
1951 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1952 if (im->symbol == ' ') {
1953 if (im->extra_flags & ALTYGRID) {
1954 sprintf(graph_label,
1955 im->ygrid_scale.labfmt,
1956 scaledstep * (double) i);
1959 sprintf(graph_label, "%4.1f",
1960 scaledstep * (double) i);
1962 sprintf(graph_label, "%4.0f",
1963 scaledstep * (double) i);
1967 char sisym = (i == 0 ? ' ' : im->symbol);
1969 if (im->extra_flags & ALTYGRID) {
1970 sprintf(graph_label,
1971 im->ygrid_scale.labfmt,
1972 scaledstep * (double) i, sisym);
1975 sprintf(graph_label, "%4.1f %c",
1976 scaledstep * (double) i, sisym);
1978 sprintf(graph_label, "%4.0f %c",
1979 scaledstep * (double) i, sisym);
1987 text_prop[TEXT_PROP_AXIS].
1989 im->graph_col[GRC_FONT],
1991 text_prop[TEXT_PROP_AXIS].
1994 text_prop[TEXT_PROP_AXIS].
1995 size, im->tabwidth, 0.0,
1996 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
1997 gfx_line(im, X0 - 2, Y0, X0, Y0,
1998 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1999 gfx_line(im, X1, Y0, X1 + 2, Y0,
2000 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2001 gfx_dashed_line(im, X0 - 2, Y0,
2007 im->grid_dash_on, im->grid_dash_off);
2008 } else if (!(im->extra_flags & NOMINOR)) {
2011 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2012 gfx_line(im, X1, Y0, X1 + 2, Y0,
2013 GRIDWIDTH, im->graph_col[GRC_GRID]);
2014 gfx_dashed_line(im, X0 - 1, Y0,
2018 graph_col[GRC_GRID],
2019 im->grid_dash_on, im->grid_dash_off);
2026 /* this is frexp for base 10 */
2037 iexp = floor(log(fabs(x)) / log(10));
2038 mnt = x / pow(10.0, iexp);
2041 mnt = x / pow(10.0, iexp);
2047 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2048 /* yes we are loosing precision by doing tos with floats instead of doubles
2049 but it seems more stable this way. */
2052 /* logaritmic horizontal grid */
2053 int horizontal_log_grid(
2057 double yloglab[][10] = {
2059 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2061 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2063 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2080 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2082 int i, j, val_exp, min_exp;
2083 double nex; /* number of decades in data */
2084 double logscale; /* scale in logarithmic space */
2085 int exfrac = 1; /* decade spacing */
2086 int mid = -1; /* row in yloglab for major grid */
2087 double mspac; /* smallest major grid spacing (pixels) */
2088 int flab; /* first value in yloglab to use */
2089 double value, tmp, pre_value;
2091 char graph_label[100];
2093 nex = log10(im->maxval / im->minval);
2094 logscale = im->ysize / nex;
2095 /* major spacing for data with high dynamic range */
2096 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2103 /* major spacing for less dynamic data */
2105 /* search best row in yloglab */
2107 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2108 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2111 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2114 /* find first value in yloglab */
2116 yloglab[mid][flab] < 10
2117 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2118 if (yloglab[mid][flab] == 10.0) {
2123 if (val_exp % exfrac)
2124 val_exp += abs(-val_exp % exfrac);
2126 X1 = im->xorigin + im->xsize;
2131 value = yloglab[mid][flab] * pow(10.0, val_exp);
2132 if (AlmostEqual2sComplement(value, pre_value, 4))
2133 break; /* it seems we are not converging */
2135 Y0 = ytr(im, value);
2136 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2138 /* major grid line */
2140 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2141 gfx_line(im, X1, Y0, X1 + 2, Y0,
2142 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2143 gfx_dashed_line(im, X0 - 2, Y0,
2148 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2150 if (im->extra_flags & FORCE_UNITS_SI) {
2155 scale = floor(val_exp / 3.0);
2157 pvalue = pow(10.0, val_exp % 3);
2159 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2160 pvalue *= yloglab[mid][flab];
2161 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2162 && ((scale + si_symbcenter) >= 0))
2163 symbol = si_symbol[scale + si_symbcenter];
2166 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2168 sprintf(graph_label, "%3.0e", value);
2172 text_prop[TEXT_PROP_AXIS].
2174 im->graph_col[GRC_FONT],
2176 text_prop[TEXT_PROP_AXIS].
2179 text_prop[TEXT_PROP_AXIS].
2180 size, im->tabwidth, 0.0,
2181 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2183 if (mid < 4 && exfrac == 1) {
2184 /* find first and last minor line behind current major line
2185 * i is the first line and j tha last */
2187 min_exp = val_exp - 1;
2188 for (i = 1; yloglab[mid][i] < 10.0; i++);
2189 i = yloglab[mid][i - 1] + 1;
2193 i = yloglab[mid][flab - 1] + 1;
2194 j = yloglab[mid][flab];
2197 /* draw minor lines below current major line */
2198 for (; i < j; i++) {
2200 value = i * pow(10.0, min_exp);
2201 if (value < im->minval)
2203 Y0 = ytr(im, value);
2204 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2209 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2210 gfx_line(im, X1, Y0, X1 + 2, Y0,
2211 GRIDWIDTH, im->graph_col[GRC_GRID]);
2212 gfx_dashed_line(im, X0 - 1, Y0,
2216 graph_col[GRC_GRID],
2217 im->grid_dash_on, im->grid_dash_off);
2219 } else if (exfrac > 1) {
2220 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2221 value = pow(10.0, i);
2222 if (value < im->minval)
2224 Y0 = ytr(im, value);
2225 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2230 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2231 gfx_line(im, X1, Y0, X1 + 2, Y0,
2232 GRIDWIDTH, im->graph_col[GRC_GRID]);
2233 gfx_dashed_line(im, X0 - 1, Y0,
2237 graph_col[GRC_GRID],
2238 im->grid_dash_on, im->grid_dash_off);
2243 if (yloglab[mid][++flab] == 10.0) {
2249 /* draw minor lines after highest major line */
2250 if (mid < 4 && exfrac == 1) {
2251 /* find first and last minor line below current major line
2252 * i is the first line and j tha last */
2254 min_exp = val_exp - 1;
2255 for (i = 1; yloglab[mid][i] < 10.0; i++);
2256 i = yloglab[mid][i - 1] + 1;
2260 i = yloglab[mid][flab - 1] + 1;
2261 j = yloglab[mid][flab];
2264 /* draw minor lines below current major line */
2265 for (; i < j; i++) {
2267 value = i * pow(10.0, min_exp);
2268 if (value < im->minval)
2270 Y0 = ytr(im, value);
2271 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2275 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2276 gfx_line(im, X1, Y0, X1 + 2, Y0,
2277 GRIDWIDTH, im->graph_col[GRC_GRID]);
2278 gfx_dashed_line(im, X0 - 1, Y0,
2282 graph_col[GRC_GRID],
2283 im->grid_dash_on, im->grid_dash_off);
2286 /* fancy minor gridlines */
2287 else if (exfrac > 1) {
2288 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2289 value = pow(10.0, i);
2290 if (value < im->minval)
2292 Y0 = ytr(im, value);
2293 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2297 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2298 gfx_line(im, X1, Y0, X1 + 2, Y0,
2299 GRIDWIDTH, im->graph_col[GRC_GRID]);
2300 gfx_dashed_line(im, X0 - 1, Y0,
2304 graph_col[GRC_GRID],
2305 im->grid_dash_on, im->grid_dash_off);
2316 int xlab_sel; /* which sort of label and grid ? */
2317 time_t ti, tilab, timajor;
2319 char graph_label[100];
2320 double X0, Y0, Y1; /* points for filled graph and more */
2323 /* the type of time grid is determined by finding
2324 the number of seconds per pixel in the graph */
2325 if (im->xlab_user.minsec == -1) {
2326 factor = (im->end - im->start) / im->xsize;
2328 while (xlab[xlab_sel + 1].minsec !=
2329 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2331 } /* pick the last one */
2332 while (xlab[xlab_sel - 1].minsec ==
2333 xlab[xlab_sel].minsec
2334 && xlab[xlab_sel].length > (im->end - im->start)) {
2336 } /* go back to the smallest size */
2337 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2338 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2339 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2340 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2341 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2342 im->xlab_user.labst = xlab[xlab_sel].labst;
2343 im->xlab_user.precis = xlab[xlab_sel].precis;
2344 im->xlab_user.stst = xlab[xlab_sel].stst;
2347 /* y coords are the same for every line ... */
2349 Y1 = im->yorigin - im->ysize;
2350 /* paint the minor grid */
2351 if (!(im->extra_flags & NOMINOR)) {
2352 for (ti = find_first_time(im->start,
2360 find_first_time(im->start,
2367 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2369 /* are we inside the graph ? */
2370 if (ti < im->start || ti > im->end)
2372 while (timajor < ti) {
2373 timajor = find_next_time(timajor,
2376 mgridtm, im->xlab_user.mgridst);
2379 continue; /* skip as falls on major grid line */
2381 gfx_line(im, X0, Y1 - 2, X0, Y1,
2382 GRIDWIDTH, im->graph_col[GRC_GRID]);
2383 gfx_line(im, X0, Y0, X0, Y0 + 2,
2384 GRIDWIDTH, im->graph_col[GRC_GRID]);
2385 gfx_dashed_line(im, X0, Y0 + 1, X0,
2388 graph_col[GRC_GRID],
2389 im->grid_dash_on, im->grid_dash_off);
2393 /* paint the major grid */
2394 for (ti = find_first_time(im->start,
2402 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2404 /* are we inside the graph ? */
2405 if (ti < im->start || ti > im->end)
2408 gfx_line(im, X0, Y1 - 2, X0, Y1,
2409 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2410 gfx_line(im, X0, Y0, X0, Y0 + 3,
2411 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2412 gfx_dashed_line(im, X0, Y0 + 3, X0,
2416 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2418 /* paint the labels below the graph */
2420 find_first_time(im->start -
2429 im->xlab_user.precis / 2;
2430 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2432 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2433 /* are we inside the graph ? */
2434 if (tilab < im->start || tilab > im->end)
2437 localtime_r(&tilab, &tm);
2438 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2440 # error "your libc has no strftime I guess we'll abort the exercise here."
2445 im->graph_col[GRC_FONT],
2447 text_prop[TEXT_PROP_AXIS].
2450 text_prop[TEXT_PROP_AXIS].
2451 size, im->tabwidth, 0.0,
2452 GFX_H_CENTER, GFX_V_TOP, graph_label);
2461 /* draw x and y axis */
2462 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2463 im->xorigin+im->xsize,im->yorigin-im->ysize,
2464 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2466 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2467 im->xorigin+im->xsize,im->yorigin-im->ysize,
2468 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2470 gfx_line(im, im->xorigin - 4,
2472 im->xorigin + im->xsize +
2473 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2474 gfx_line(im, im->xorigin,
2477 im->yorigin - im->ysize -
2478 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2479 /* arrow for X and Y axis direction */
2480 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 */
2481 im->graph_col[GRC_ARROW]);
2483 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 */
2484 im->graph_col[GRC_ARROW]);
2493 double X0, Y0; /* points for filled graph and more */
2494 struct gfx_color_t water_color;
2496 /* draw 3d border */
2497 gfx_new_area(im, 0, im->yimg,
2498 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2499 gfx_add_point(im, im->ximg - 2, 2);
2500 gfx_add_point(im, im->ximg, 0);
2501 gfx_add_point(im, 0, 0);
2503 gfx_new_area(im, 2, im->yimg - 2,
2505 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2506 gfx_add_point(im, im->ximg, 0);
2507 gfx_add_point(im, im->ximg, im->yimg);
2508 gfx_add_point(im, 0, im->yimg);
2510 if (im->draw_x_grid == 1)
2512 if (im->draw_y_grid == 1) {
2513 if (im->logarithmic) {
2514 res = horizontal_log_grid(im);
2516 res = draw_horizontal_grid(im);
2519 /* dont draw horizontal grid if there is no min and max val */
2521 char *nodata = "No Data found";
2523 gfx_text(im, im->ximg / 2,
2526 im->graph_col[GRC_FONT],
2528 text_prop[TEXT_PROP_AXIS].
2531 text_prop[TEXT_PROP_AXIS].
2532 size, im->tabwidth, 0.0,
2533 GFX_H_CENTER, GFX_V_CENTER, nodata);
2537 /* yaxis unit description */
2542 im->graph_col[GRC_FONT],
2544 text_prop[TEXT_PROP_UNIT].
2547 text_prop[TEXT_PROP_UNIT].
2549 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2553 im->graph_col[GRC_FONT],
2555 text_prop[TEXT_PROP_TITLE].
2558 text_prop[TEXT_PROP_TITLE].
2559 size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2560 /* rrdtool 'logo' */
2561 water_color = im->graph_col[GRC_FONT];
2562 water_color.alpha = 0.3;
2563 gfx_text(im, im->ximg - 4, 5,
2566 text_prop[TEXT_PROP_AXIS].
2567 font, 5.5, im->tabwidth,
2568 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2569 /* graph watermark */
2570 if (im->watermark[0] != '\0') {
2572 im->ximg / 2, im->yimg - 6,
2575 text_prop[TEXT_PROP_AXIS].
2576 font, 5.5, im->tabwidth, 0,
2577 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2581 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2582 for (i = 0; i < im->gdes_c; i++) {
2583 if (im->gdes[i].legend[0] == '\0')
2585 /* im->gdes[i].leg_y is the bottom of the legend */
2586 X0 = im->gdes[i].leg_x;
2587 Y0 = im->gdes[i].leg_y;
2588 gfx_text(im, X0, Y0,
2589 im->graph_col[GRC_FONT],
2592 [TEXT_PROP_LEGEND].font,
2595 [TEXT_PROP_LEGEND].size,
2597 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2598 /* The legend for GRAPH items starts with "M " to have
2599 enough space for the box */
2600 if (im->gdes[i].gf != GF_PRINT &&
2601 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2605 boxH = gfx_get_text_width(im, 0,
2613 size, im->tabwidth, "o") * 1.2;
2615 /* shift the box up a bit */
2617 /* make sure transparent colors show up the same way as in the graph */
2620 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2621 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2623 gfx_new_area(im, X0, Y0 - boxV, X0,
2624 Y0, X0 + boxH, Y0, im->gdes[i].col);
2625 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2628 cairo_new_path(im->cr);
2629 cairo_set_line_width(im->cr, 1.0);
2632 gfx_line_fit(im, &X0, &Y0);
2633 gfx_line_fit(im, &X1, &Y1);
2634 cairo_move_to(im->cr, X0, Y0);
2635 cairo_line_to(im->cr, X1, Y0);
2636 cairo_line_to(im->cr, X1, Y1);
2637 cairo_line_to(im->cr, X0, Y1);
2638 cairo_close_path(im->cr);
2639 cairo_set_source_rgba(im->cr,
2651 blue, im->graph_col[GRC_FRAME].alpha);
2652 if (im->gdes[i].dash) {
2653 // make box borders in legend dashed if the graph is dashed
2657 cairo_set_dash(im->cr, dashes, 1, 0.0);
2659 cairo_stroke(im->cr);
2660 cairo_restore(im->cr);
2667 /*****************************************************
2668 * lazy check make sure we rely need to create this graph
2669 *****************************************************/
2676 struct stat imgstat;
2679 return 0; /* no lazy option */
2680 if (strlen(im->graphfile) == 0)
2681 return 0; /* inmemory option */
2682 if (stat(im->graphfile, &imgstat) != 0)
2683 return 0; /* can't stat */
2684 /* one pixel in the existing graph is more then what we would
2686 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2688 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2689 return 0; /* the file does not exist */
2690 switch (im->imgformat) {
2692 size = PngSize(fd, &(im->ximg), &(im->yimg));
2702 int graph_size_location(
2707 /* The actual size of the image to draw is determined from
2708 ** several sources. The size given on the command line is
2709 ** the graph area but we need more as we have to draw labels
2710 ** and other things outside the graph area
2713 int Xvertical = 0, Ytitle =
2714 0, Xylabel = 0, Xmain = 0, Ymain =
2715 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2718 if (im->extra_flags & ONLY_GRAPH) {
2720 im->ximg = im->xsize;
2721 im->yimg = im->ysize;
2722 im->yorigin = im->ysize;
2727 /** +---+--------------------------------------------+
2728 ** | y |...............graph title..................|
2729 ** | +---+-------------------------------+--------+
2732 ** | i | a | | pie |
2733 ** | s | x | main graph area | chart |
2738 ** | l | b +-------------------------------+--------+
2739 ** | e | l | x axis labels | |
2740 ** +---+---+-------------------------------+--------+
2741 ** |....................legends.....................|
2742 ** +------------------------------------------------+
2744 ** +------------------------------------------------+
2747 if (im->ylegend[0] != '\0') {
2748 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2751 if (im->title[0] != '\0') {
2752 /* The title is placed "inbetween" two text lines so it
2753 ** automatically has some vertical spacing. The horizontal
2754 ** spacing is added here, on each side.
2756 /* if necessary, reduce the font size of the title until it fits the image width */
2757 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2761 if (im->draw_x_grid) {
2762 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2764 if (im->draw_y_grid || im->forceleftspace) {
2766 gfx_get_text_width(im, 0,
2774 size, im->tabwidth, "0") * im->unitslength;
2778 if (im->extra_flags & FULL_SIZE_MODE) {
2779 /* The actual size of the image to draw has been determined by the user.
2780 ** The graph area is the space remaining after accounting for the legend,
2781 ** the watermark, the pie chart, the axis labels, and the title.
2784 im->ximg = im->xsize;
2785 im->yimg = im->ysize;
2786 im->yorigin = im->ysize;
2789 im->yorigin += Ytitle;
2790 /* Now calculate the total size. Insert some spacing where
2791 desired. im->xorigin and im->yorigin need to correspond
2792 with the lower left corner of the main graph area or, if
2793 this one is not set, the imaginary box surrounding the
2795 /* Initial size calculation for the main graph area */
2796 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2798 Xmain -= Xspacing; /* put space between main graph area and right edge */
2799 im->xorigin = Xspacing + Xylabel;
2800 /* the length of the title should not influence with width of the graph
2801 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2802 if (Xvertical) { /* unit description */
2804 im->xorigin += Xvertical;
2808 /* The vertical size of the image is known in advance. The main graph area
2809 ** (Ymain) and im->yorigin must be set according to the space requirements
2810 ** of the legend and the axis labels.
2812 if (im->extra_flags & NOLEGEND) {
2813 /* set dimensions correctly if using full size mode with no legend */
2816 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2817 Ymain = im->yorigin;
2819 /* Determine where to place the legends onto the image.
2820 ** Set Ymain and adjust im->yorigin to match the space requirements.
2822 if (leg_place(im, &Ymain) == -1)
2827 /* remove title space *or* some padding above the graph from the main graph area */
2831 Ymain -= 1.5 * Yspacing;
2834 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2835 if (im->watermark[0] != '\0') {
2836 Ymain -= Ywatermark;
2840 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2842 /* The actual size of the image to draw is determined from
2843 ** several sources. The size given on the command line is
2844 ** the graph area but we need more as we have to draw labels
2845 ** and other things outside the graph area.
2848 if (im->ylegend[0] != '\0') {
2849 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2853 if (im->title[0] != '\0') {
2854 /* The title is placed "inbetween" two text lines so it
2855 ** automatically has some vertical spacing. The horizontal
2856 ** spacing is added here, on each side.
2858 /* don't care for the with of the title
2859 Xtitle = gfx_get_text_width(im->canvas, 0,
2860 im->text_prop[TEXT_PROP_TITLE].font,
2861 im->text_prop[TEXT_PROP_TITLE].size,
2863 im->title, 0) + 2*Xspacing; */
2864 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2871 /* Now calculate the total size. Insert some spacing where
2872 desired. im->xorigin and im->yorigin need to correspond
2873 with the lower left corner of the main graph area or, if
2874 this one is not set, the imaginary box surrounding the
2877 /* The legend width cannot yet be determined, as a result we
2878 ** have problems adjusting the image to it. For now, we just
2879 ** forget about it at all; the legend will have to fit in the
2880 ** size already allocated.
2882 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2884 im->ximg += Xspacing;
2885 im->xorigin = Xspacing + Xylabel;
2886 /* the length of the title should not influence with width of the graph
2887 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2888 if (Xvertical) { /* unit description */
2889 im->ximg += Xvertical;
2890 im->xorigin += Xvertical;
2893 /* The vertical size is interesting... we need to compare
2894 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2895 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2896 ** in order to start even thinking about Ylegend or Ywatermark.
2898 ** Do it in three portions: First calculate the inner part,
2899 ** then do the legend, then adjust the total height of the img,
2900 ** adding space for a watermark if one exists;
2902 /* reserve space for main and/or pie */
2903 im->yimg = Ymain + Yxlabel;
2904 im->yorigin = im->yimg - Yxlabel;
2905 /* reserve space for the title *or* some padding above the graph */
2908 im->yorigin += Ytitle;
2910 im->yimg += 1.5 * Yspacing;
2911 im->yorigin += 1.5 * Yspacing;
2913 /* reserve space for padding below the graph */
2914 im->yimg += Yspacing;
2915 /* Determine where to place the legends onto the image.
2916 ** Adjust im->yimg to match the space requirements.
2918 if (leg_place(im, 0) == -1)
2920 if (im->watermark[0] != '\0') {
2921 im->yimg += Ywatermark;
2926 info.u_cnt = im->xorigin;
2927 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2928 info.u_cnt = im->yorigin - Ymain;
2929 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2930 info.u_cnt = im->xsize;
2931 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2932 info.u_cnt = im->ysize;
2933 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2934 info.u_cnt = im->ximg;
2935 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2936 info.u_cnt = im->yimg;
2937 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2941 static cairo_status_t cairo_output(
2945 unsigned int length)
2947 image_desc_t *im = closure;
2949 im->rendered_image =
2950 realloc(im->rendered_image, im->rendered_image_size + length);
2951 if (im->rendered_image == NULL)
2952 return CAIRO_STATUS_WRITE_ERROR;
2953 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2954 im->rendered_image_size += length;
2955 return CAIRO_STATUS_SUCCESS;
2958 /* draw that picture thing ... */
2963 int lazy = lazy_check(im);
2964 double areazero = 0.0;
2965 graph_desc_t *lastgdes = NULL;
2967 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2969 /* if we are lazy and there is nothing to PRINT ... quit now */
2970 if (lazy && im->prt_c == 0)
2972 /* pull the data from the rrd files ... */
2973 if (data_fetch(im) == -1)
2975 /* evaluate VDEF and CDEF operations ... */
2976 if (data_calc(im) == -1)
2978 /* calculate and PRINT and GPRINT definitions. We have to do it at
2979 * this point because it will affect the length of the legends
2980 * if there are no graph elements we stop here ...
2981 * if we are lazy, try to quit ...
2986 if ((i == 0) || lazy)
2988 /**************************************************************
2989 *** Calculating sizes and locations became a bit confusing ***
2990 *** so I moved this into a separate function. ***
2991 **************************************************************/
2992 if (graph_size_location(im, i) == -1)
2994 /* get actual drawing data and find min and max values */
2995 if (data_proc(im) == -1)
2997 if (!im->logarithmic) {
3001 /* identify si magnitude Kilo, Mega Giga ? */
3002 if (!im->rigid && !im->logarithmic)
3003 expand_range(im); /* make sure the upper and lower limit are
3006 info.u_val = im->minval;
3007 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3008 info.u_val = im->maxval;
3009 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3011 if (!calc_horizontal_grid(im))
3016 apply_gridfit(im); */
3017 /* the actual graph is created by going through the individual
3018 graph elements and then drawing them */
3019 cairo_surface_destroy(im->surface);
3020 switch (im->imgformat) {
3023 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3024 im->ximg * im->zoom,
3025 im->yimg * im->zoom);
3029 im->surface = strlen(im->graphfile)
3031 cairo_pdf_surface_create_for_stream
3032 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3033 : cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3034 im->yimg * im->zoom);
3038 im->surface = strlen(im->graphfile)
3040 cairo_ps_surface_create_for_stream
3041 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3042 : cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3043 im->yimg * im->zoom);
3047 im->surface = strlen(im->graphfile)
3049 cairo_svg_surface_create_for_stream
3050 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3051 : cairo_svg_surface_create(im->
3054 ximg * im->zoom, im->yimg * im->zoom);
3055 cairo_svg_surface_restrict_to_version
3056 (im->surface, CAIRO_SVG_VERSION_1_1);
3059 im->cr = cairo_create(im->surface);
3060 cairo_set_antialias(im->cr, im->graph_antialias);
3061 cairo_scale(im->cr, im->zoom, im->zoom);
3062 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3063 gfx_new_area(im, 0, 0, 0, im->yimg,
3064 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3065 gfx_add_point(im, im->ximg, 0);
3067 gfx_new_area(im, im->xorigin,
3070 im->xsize, im->yorigin,
3073 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3074 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3076 if (im->minval > 0.0)
3077 areazero = im->minval;
3078 if (im->maxval < 0.0)
3079 areazero = im->maxval;
3080 for (i = 0; i < im->gdes_c; i++) {
3081 switch (im->gdes[i].gf) {
3095 for (ii = 0; ii < im->xsize; ii++) {
3096 if (!isnan(im->gdes[i].p_data[ii])
3097 && im->gdes[i].p_data[ii] != 0.0) {
3098 if (im->gdes[i].yrule > 0) {
3105 im->ysize, 1.0, im->gdes[i].col);
3106 } else if (im->gdes[i].yrule < 0) {
3109 im->yorigin - im->ysize,
3114 im->ysize, 1.0, im->gdes[i].col);
3121 /* fix data points at oo and -oo */
3122 for (ii = 0; ii < im->xsize; ii++) {
3123 if (isinf(im->gdes[i].p_data[ii])) {
3124 if (im->gdes[i].p_data[ii] > 0) {
3125 im->gdes[i].p_data[ii] = im->maxval;
3127 im->gdes[i].p_data[ii] = im->minval;
3133 /* *******************************************************
3138 -------|--t-1--t--------------------------------
3140 if we know the value at time t was a then
3141 we draw a square from t-1 to t with the value a.
3143 ********************************************************* */
3144 if (im->gdes[i].col.alpha != 0.0) {
3145 /* GF_LINE and friend */
3146 if (im->gdes[i].gf == GF_LINE) {
3147 double last_y = 0.0;
3151 cairo_new_path(im->cr);
3152 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3153 if (im->gdes[i].dash) {
3154 cairo_set_dash(im->cr,
3155 im->gdes[i].p_dashes,
3156 im->gdes[i].ndash, im->gdes[i].offset);
3159 for (ii = 1; ii < im->xsize; ii++) {
3160 if (isnan(im->gdes[i].p_data[ii])
3161 || (im->slopemode == 1
3162 && isnan(im->gdes[i].p_data[ii - 1]))) {
3167 last_y = ytr(im, im->gdes[i].p_data[ii]);
3168 if (im->slopemode == 0) {
3169 double x = ii - 1 + im->xorigin;
3172 gfx_line_fit(im, &x, &y);
3173 cairo_move_to(im->cr, x, y);
3174 x = ii + im->xorigin;
3176 gfx_line_fit(im, &x, &y);
3177 cairo_line_to(im->cr, x, y);
3179 double x = ii - 1 + im->xorigin;
3181 ytr(im, im->gdes[i].p_data[ii - 1]);
3182 gfx_line_fit(im, &x, &y);
3183 cairo_move_to(im->cr, x, y);
3184 x = ii + im->xorigin;
3186 gfx_line_fit(im, &x, &y);
3187 cairo_line_to(im->cr, x, y);
3191 double x1 = ii + im->xorigin;
3192 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3194 if (im->slopemode == 0
3195 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3196 double x = ii - 1 + im->xorigin;
3199 gfx_line_fit(im, &x, &y);
3200 cairo_line_to(im->cr, x, y);
3203 gfx_line_fit(im, &x1, &y1);
3204 cairo_line_to(im->cr, x1, y1);
3207 cairo_set_source_rgba(im->cr,
3213 col.blue, im->gdes[i].col.alpha);
3214 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3215 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3216 cairo_stroke(im->cr);
3217 cairo_restore(im->cr);
3221 (double *) malloc(sizeof(double) * im->xsize * 2);
3223 (double *) malloc(sizeof(double) * im->xsize * 2);
3225 (double *) malloc(sizeof(double) * im->xsize * 2);
3227 (double *) malloc(sizeof(double) * im->xsize * 2);
3230 for (ii = 0; ii <= im->xsize; ii++) {
3233 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3239 AlmostEqual2sComplement(foreY
3243 AlmostEqual2sComplement(foreY
3253 foreY[cntI], im->gdes[i].col);
3254 while (cntI < idxI) {
3259 AlmostEqual2sComplement(foreY
3263 AlmostEqual2sComplement(foreY
3270 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3272 gfx_add_point(im, backX[idxI], backY[idxI]);
3278 AlmostEqual2sComplement(backY
3282 AlmostEqual2sComplement(backY
3289 gfx_add_point(im, backX[idxI], backY[idxI]);
3299 if (ii == im->xsize)
3301 if (im->slopemode == 0 && ii == 0) {
3304 if (isnan(im->gdes[i].p_data[ii])) {
3308 ytop = ytr(im, im->gdes[i].p_data[ii]);
3309 if (lastgdes && im->gdes[i].stack) {
3310 ybase = ytr(im, lastgdes->p_data[ii]);
3312 ybase = ytr(im, areazero);
3314 if (ybase == ytop) {
3320 double extra = ytop;
3325 if (im->slopemode == 0) {
3326 backY[++idxI] = ybase - 0.2;
3327 backX[idxI] = ii + im->xorigin - 1;
3328 foreY[idxI] = ytop + 0.2;
3329 foreX[idxI] = ii + im->xorigin - 1;
3331 backY[++idxI] = ybase - 0.2;
3332 backX[idxI] = ii + im->xorigin;
3333 foreY[idxI] = ytop + 0.2;
3334 foreX[idxI] = ii + im->xorigin;
3336 /* close up any remaining area */
3341 } /* else GF_LINE */
3343 /* if color != 0x0 */
3344 /* make sure we do not run into trouble when stacking on NaN */
3345 for (ii = 0; ii < im->xsize; ii++) {
3346 if (isnan(im->gdes[i].p_data[ii])) {
3347 if (lastgdes && (im->gdes[i].stack)) {
3348 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3350 im->gdes[i].p_data[ii] = areazero;
3354 lastgdes = &(im->gdes[i]);
3358 ("STACK should already be turned into LINE or AREA here");
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", 6) == 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, (stream ? stream : stdout));
3583 /* skip anything else */
3584 walker = walker->next;
3591 /* Some surgery done on this function, it became ridiculously big.
3593 ** - initializing now in rrd_graph_init()
3594 ** - options parsing now in rrd_graph_options()
3595 ** - script parsing now in rrd_graph_script()
3597 info_t * rrd_graph_v(
3603 rrd_graph_init(&im);
3604 /* a dummy surface so that we can measure text sizes for placements */
3605 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3606 im.cr = cairo_create(im.surface);
3607 rrd_graph_options(argc, argv, &im);
3608 if (rrd_test_error()) {
3609 info_free(im.grinfo);
3614 if (optind >= argc) {
3615 info_free(im.grinfo);
3617 rrd_set_error("missing filename");
3621 if (strlen(argv[optind]) >= MAXPATH) {
3622 rrd_set_error("filename (including path) too long");
3623 info_free(im.grinfo);
3628 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3629 im.graphfile[MAXPATH - 1] = '\0';
3631 if (strcmp(im.graphfile, "-") == 0) {
3632 im.graphfile[0] = '\0';
3635 rrd_graph_script(argc, argv, &im, 1);
3636 if (rrd_test_error()) {
3637 info_free(im.grinfo);
3642 /* Everything is now read and the actual work can start */
3644 if (graph_paint(&im) == -1) {
3645 info_free(im.grinfo);
3651 /* The image is generated and needs to be output.
3652 ** Also, if needed, print a line with information about the image.
3659 sprintf_alloc(im.imginfo,
3662 im.ximg), (long) (im.zoom * im.yimg));
3663 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3666 if (im.rendered_image) {
3668 img.u_blo.size = im.rendered_image_size;
3669 img.u_blo.ptr = im.rendered_image;
3670 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3677 void rrd_graph_init(
3686 #ifdef HAVE_SETLOCALE
3687 setlocale(LC_TIME, "");
3688 #ifdef HAVE_MBSTOWCS
3689 setlocale(LC_CTYPE, "");
3694 im->draw_x_grid = 1;
3695 im->draw_y_grid = 1;
3696 im->extra_flags = 0;
3697 im->font_options = cairo_font_options_create();
3698 im->forceleftspace = 0;
3701 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3702 im->grid_dash_off = 1;
3703 im->grid_dash_on = 1;
3705 im->grinfo = (info_t *) NULL;
3706 im->grinfo_current = (info_t *) NULL;
3707 im->imgformat = IF_PNG;
3710 im->logarithmic = 0;
3716 im->rendered_image_size = 0;
3717 im->rendered_image = NULL;
3722 im->tabwidth = 40.0;
3723 im->title[0] = '\0';
3724 im->unitsexponent = 9999;
3725 im->unitslength = 6;
3726 im->viewfactor = 1.0;
3727 im->watermark[0] = '\0';
3729 im->xlab_user.minsec = -1;
3732 im->ygridstep = DNAN;
3734 im->ylegend[0] = '\0';
3738 cairo_font_options_set_hint_style
3739 (im->font_options, CAIRO_HINT_STYLE_FULL);
3740 cairo_font_options_set_hint_metrics
3741 (im->font_options, CAIRO_HINT_METRICS_ON);
3742 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3743 for (i = 0; i < DIM(graph_col); i++)
3744 im->graph_col[i] = graph_col[i];
3745 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3748 char rrd_win_default_font[1000];
3750 windir = getenv("windir");
3751 /* %windir% is something like D:\windows or C:\winnt */
3752 if (windir != NULL) {
3753 strncpy(rrd_win_default_font, windir, 500);
3754 rrd_win_default_font[500] = '\0';
3755 strcat(rrd_win_default_font, "\\fonts\\");
3756 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3757 for (i = 0; i < DIM(text_prop); i++) {
3758 strncpy(text_prop[i].font,
3759 rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3760 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3768 deffont = getenv("RRD_DEFAULT_FONT");
3769 if (deffont != NULL) {
3770 for (i = 0; i < DIM(text_prop); i++) {
3771 strncpy(text_prop[i].font, deffont,
3772 sizeof(text_prop[i].font) - 1);
3773 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3777 for (i = 0; i < DIM(text_prop); i++) {
3778 im->text_prop[i].size = text_prop[i].size;
3779 strcpy(im->text_prop[i].font, text_prop[i].font);
3783 void rrd_graph_options(
3790 char *parsetime_error = NULL;
3791 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3792 time_t start_tmp = 0, end_tmp = 0;
3794 struct rrd_time_value start_tv, end_tv;
3795 long unsigned int color;
3796 char *old_locale = "";
3798 /* defines for long options without a short equivalent. should be bytes,
3799 and may not collide with (the ASCII value of) short options */
3800 #define LONGOPT_UNITS_SI 255
3801 struct option long_options[] = {
3803 "start", required_argument, 0, 's'}, {
3804 "end", required_argument, 0,
3807 required_argument, 0,
3821 "height", required_argument, 0, 'h'}, {
3822 "full-size-mode", no_argument,
3840 "base", required_argument, 0, 'b'}, {
3841 "logarithmic", no_argument, 0,
3844 required_argument, 0,
3853 "imginfo", required_argument, 0, 'f'}, {
3855 required_argument, 0, 'a'}, {
3861 "zoom", required_argument, 0, 'm'}, {
3862 "no-legend", no_argument, 0,
3864 "force-rules-legend",
3874 "no-minor", no_argument, 0, 'I'}, {
3875 "slope-mode", no_argument, 0,
3878 no_argument, 0, 'A'}, {
3879 "alt-autoscale-min",
3883 "alt-autoscale-max",
3892 "units-exponent", required_argument,
3894 "units-length", required_argument,
3896 "units", required_argument, 0,
3897 LONGOPT_UNITS_SI}, {
3898 "step", required_argument, 0,
3901 required_argument, 0,
3906 "graph-render-mode",
3911 "font-smoothing-threshold",
3912 required_argument, 0, 'B'}, {
3913 "watermark", required_argument, 0, 'W'}, {
3914 "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 */
3919 opterr = 0; /* initialize getopt */
3920 parsetime("end-24h", &start_tv);
3921 parsetime("now", &end_tv);
3923 int option_index = 0;
3925 int col_start, col_end;
3927 opt = getopt_long(argc, argv,
3928 "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",
3929 long_options, &option_index);
3934 im->extra_flags |= NOMINOR;
3937 im->extra_flags |= ALTYGRID;
3940 im->extra_flags |= ALTAUTOSCALE;
3943 im->extra_flags |= ALTAUTOSCALE_MIN;
3946 im->extra_flags |= ALTAUTOSCALE_MAX;
3949 im->extra_flags |= ONLY_GRAPH;
3952 im->extra_flags |= NOLEGEND;
3955 im->extra_flags |= FORCE_RULES_LEGEND;
3957 case LONGOPT_UNITS_SI:
3958 if (im->extra_flags & FORCE_UNITS) {
3959 rrd_set_error("--units can only be used once!");
3960 setlocale(LC_NUMERIC, old_locale);
3963 if (strcmp(optarg, "si") == 0)
3964 im->extra_flags |= FORCE_UNITS_SI;
3966 rrd_set_error("invalid argument for --units: %s", optarg);
3971 im->unitsexponent = atoi(optarg);
3974 im->unitslength = atoi(optarg);
3975 im->forceleftspace = 1;
3978 old_locale = setlocale(LC_NUMERIC, "C");
3979 im->tabwidth = atof(optarg);
3980 setlocale(LC_NUMERIC, old_locale);
3983 old_locale = setlocale(LC_NUMERIC, "C");
3984 im->step = atoi(optarg);
3985 setlocale(LC_NUMERIC, old_locale);
3991 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3992 rrd_set_error("start time: %s", parsetime_error);
3997 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3998 rrd_set_error("end time: %s", parsetime_error);
4003 if (strcmp(optarg, "none") == 0) {
4004 im->draw_x_grid = 0;
4008 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4010 &im->xlab_user.gridst,
4012 &im->xlab_user.mgridst,
4014 &im->xlab_user.labst,
4015 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4016 strncpy(im->xlab_form, optarg + stroff,
4017 sizeof(im->xlab_form) - 1);
4018 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4020 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4021 rrd_set_error("unknown keyword %s", scan_gtm);
4024 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4026 rrd_set_error("unknown keyword %s", scan_mtm);
4029 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4030 rrd_set_error("unknown keyword %s", scan_ltm);
4033 im->xlab_user.minsec = 1;
4034 im->xlab_user.stst = im->xlab_form;
4036 rrd_set_error("invalid x-grid format");
4042 if (strcmp(optarg, "none") == 0) {
4043 im->draw_y_grid = 0;
4046 old_locale = setlocale(LC_NUMERIC, "C");
4047 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4048 setlocale(LC_NUMERIC, old_locale);
4049 if (im->ygridstep <= 0) {
4050 rrd_set_error("grid step must be > 0");
4052 } else if (im->ylabfact < 1) {
4053 rrd_set_error("label factor must be > 0");
4057 setlocale(LC_NUMERIC, old_locale);
4058 rrd_set_error("invalid y-grid format");
4063 strncpy(im->ylegend, optarg, 150);
4064 im->ylegend[150] = '\0';
4067 old_locale = setlocale(LC_NUMERIC, "C");
4068 im->maxval = atof(optarg);
4069 setlocale(LC_NUMERIC, old_locale);
4072 old_locale = setlocale(LC_NUMERIC, "C");
4073 im->minval = atof(optarg);
4074 setlocale(LC_NUMERIC, old_locale);
4077 im->base = atol(optarg);
4078 if (im->base != 1024 && im->base != 1000) {
4080 ("the only sensible value for base apart from 1000 is 1024");
4085 long_tmp = atol(optarg);
4086 if (long_tmp < 10) {
4087 rrd_set_error("width below 10 pixels");
4090 im->xsize = long_tmp;
4093 long_tmp = atol(optarg);
4094 if (long_tmp < 10) {
4095 rrd_set_error("height below 10 pixels");
4098 im->ysize = long_tmp;
4101 im->extra_flags |= FULL_SIZE_MODE;
4104 /* interlaced png not supported at the moment */
4110 im->imginfo = optarg;
4114 (im->imgformat = if_conv(optarg)) == -1) {
4115 rrd_set_error("unsupported graphics format '%s'", optarg);
4126 im->logarithmic = 1;
4130 "%10[A-Z]#%n%8lx%n",
4131 col_nam, &col_start, &color, &col_end) == 2) {
4133 int col_len = col_end - col_start;
4138 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4146 (((color & 0xF000) *
4147 0x11000) | ((color & 0x0F00) *
4148 0x01100) | ((color &
4151 ((color & 0x000F) * 0x00011)
4155 color = (color << 8) + 0xff /* shift left by 8 */ ;
4160 rrd_set_error("the color format is #RRGGBB[AA]");
4163 if ((ci = grc_conv(col_nam)) != -1) {
4164 im->graph_col[ci] = gfx_hex_to_col(color);
4166 rrd_set_error("invalid color name '%s'", col_nam);
4170 rrd_set_error("invalid color def format");
4179 old_locale = setlocale(LC_NUMERIC, "C");
4180 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4181 int sindex, propidx;
4183 setlocale(LC_NUMERIC, old_locale);
4184 if ((sindex = text_prop_conv(prop)) != -1) {
4185 for (propidx = sindex;
4186 propidx < TEXT_PROP_LAST; propidx++) {
4188 im->text_prop[propidx].size = size;
4190 if ((int) strlen(prop) > end) {
4191 if (prop[end] == ':') {
4192 strncpy(im->text_prop[propidx].font,
4193 prop + end + 1, 255);
4194 im->text_prop[propidx].font[255] = '\0';
4197 ("expected after font size in '%s'",
4202 if (propidx == sindex && sindex != 0)
4206 rrd_set_error("invalid fonttag '%s'", prop);
4210 setlocale(LC_NUMERIC, old_locale);
4211 rrd_set_error("invalid text property format");
4217 old_locale = setlocale(LC_NUMERIC, "C");
4218 im->zoom = atof(optarg);
4219 setlocale(LC_NUMERIC, old_locale);
4220 if (im->zoom <= 0.0) {
4221 rrd_set_error("zoom factor must be > 0");
4226 strncpy(im->title, optarg, 150);
4227 im->title[150] = '\0';
4230 if (strcmp(optarg, "normal") == 0) {
4231 cairo_font_options_set_antialias
4232 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4233 cairo_font_options_set_hint_style
4234 (im->font_options, CAIRO_HINT_STYLE_FULL);
4235 } else if (strcmp(optarg, "light") == 0) {
4236 cairo_font_options_set_antialias
4237 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4238 cairo_font_options_set_hint_style
4239 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4240 } else if (strcmp(optarg, "mono") == 0) {
4241 cairo_font_options_set_antialias
4242 (im->font_options, CAIRO_ANTIALIAS_NONE);
4243 cairo_font_options_set_hint_style
4244 (im->font_options, CAIRO_HINT_STYLE_FULL);
4246 rrd_set_error("unknown font-render-mode '%s'", optarg);
4251 if (strcmp(optarg, "normal") == 0)
4252 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4253 else if (strcmp(optarg, "mono") == 0)
4254 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4256 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4261 /* not supported curently */
4264 strncpy(im->watermark, optarg, 100);
4265 im->watermark[99] = '\0';
4269 rrd_set_error("unknown option '%c'", optopt);
4271 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4276 if (im->logarithmic == 1 && im->minval <= 0) {
4278 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4282 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4283 /* error string is set in parsetime.c */
4287 if (start_tmp < 3600 * 24 * 365 * 10) {
4289 ("the first entry to fetch should be after 1980 (%ld)",
4294 if (end_tmp < start_tmp) {
4296 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4300 im->start = start_tmp;
4302 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4305 int rrd_graph_color(
4313 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4315 color = strstr(var, "#");
4316 if (color == NULL) {
4317 if (optional == 0) {
4318 rrd_set_error("Found no color in %s", err);
4325 long unsigned int col;
4327 rest = strstr(color, ":");
4334 sscanf(color, "#%6lx%n", &col, &n);
4335 col = (col << 8) + 0xff /* shift left by 8 */ ;
4337 rrd_set_error("Color problem in %s", err);
4340 sscanf(color, "#%8lx%n", &col, &n);
4344 rrd_set_error("Color problem in %s", err);
4346 if (rrd_test_error())
4348 gdp->col = gfx_hex_to_col(col);
4361 while (*ptr != '\0')
4362 if (*ptr++ == '%') {
4364 /* line cannot end with percent char */
4367 /* '%s', '%S' and '%%' are allowed */
4368 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4370 /* %c is allowed (but use only with vdef!) */
4371 else if (*ptr == 'c') {
4376 /* or else '% 6.2lf' and such are allowed */
4378 /* optional padding character */
4379 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4381 /* This should take care of 'm.n' with all three optional */
4382 while (*ptr >= '0' && *ptr <= '9')
4386 while (*ptr >= '0' && *ptr <= '9')
4388 /* Either 'le', 'lf' or 'lg' must follow here */
4391 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4406 const char *const str)
4408 /* A VDEF currently is either "func" or "param,func"
4409 * so the parsing is rather simple. Change if needed.
4417 old_locale = setlocale(LC_NUMERIC, "C");
4418 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4419 setlocale(LC_NUMERIC, old_locale);
4420 if (n == (int) strlen(str)) { /* matched */
4424 sscanf(str, "%29[A-Z]%n", func, &n);
4425 if (n == (int) strlen(str)) { /* matched */
4429 ("Unknown function string '%s' in VDEF '%s'",
4434 if (!strcmp("PERCENT", func))
4435 gdes->vf.op = VDEF_PERCENT;
4436 else if (!strcmp("MAXIMUM", func))
4437 gdes->vf.op = VDEF_MAXIMUM;
4438 else if (!strcmp("AVERAGE", func))
4439 gdes->vf.op = VDEF_AVERAGE;
4440 else if (!strcmp("STDEV", func))
4441 gdes->vf.op = VDEF_STDEV;
4442 else if (!strcmp("MINIMUM", func))
4443 gdes->vf.op = VDEF_MINIMUM;
4444 else if (!strcmp("TOTAL", func))
4445 gdes->vf.op = VDEF_TOTAL;
4446 else if (!strcmp("FIRST", func))
4447 gdes->vf.op = VDEF_FIRST;
4448 else if (!strcmp("LAST", func))
4449 gdes->vf.op = VDEF_LAST;
4450 else if (!strcmp("LSLSLOPE", func))
4451 gdes->vf.op = VDEF_LSLSLOPE;
4452 else if (!strcmp("LSLINT", func))
4453 gdes->vf.op = VDEF_LSLINT;
4454 else if (!strcmp("LSLCORREL", func))
4455 gdes->vf.op = VDEF_LSLCORREL;
4458 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4461 switch (gdes->vf.op) {
4463 if (isnan(param)) { /* no parameter given */
4465 ("Function '%s' needs parameter in VDEF '%s'\n",
4469 if (param >= 0.0 && param <= 100.0) {
4470 gdes->vf.param = param;
4471 gdes->vf.val = DNAN; /* undefined */
4472 gdes->vf.when = 0; /* undefined */
4475 ("Parameter '%f' out of range in VDEF '%s'\n",
4476 param, gdes->vname);
4489 case VDEF_LSLCORREL:
4491 gdes->vf.param = DNAN;
4492 gdes->vf.val = DNAN;
4496 ("Function '%s' needs no parameter in VDEF '%s'\n",
4510 graph_desc_t *src, *dst;
4514 dst = &im->gdes[gdi];
4515 src = &im->gdes[dst->vidx];
4516 data = src->data + src->ds;
4517 steps = (src->end - src->start) / src->step;
4520 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4521 src->start, src->end, steps);
4523 switch (dst->vf.op) {
4527 if ((array = malloc(steps * sizeof(double))) == NULL) {
4528 rrd_set_error("malloc VDEV_PERCENT");
4531 for (step = 0; step < steps; step++) {
4532 array[step] = data[step * src->ds_cnt];
4534 qsort(array, step, sizeof(double), vdef_percent_compar);
4535 field = (steps - 1) * dst->vf.param / 100;
4536 dst->vf.val = array[field];
4537 dst->vf.when = 0; /* no time component */
4540 for (step = 0; step < steps; step++)
4541 printf("DEBUG: %3li:%10.2f %c\n",
4542 step, array[step], step == field ? '*' : ' ');
4548 while (step != steps && isnan(data[step * src->ds_cnt]))
4550 if (step == steps) {
4554 dst->vf.val = data[step * src->ds_cnt];
4555 dst->vf.when = src->start + (step + 1) * src->step;
4557 while (step != steps) {
4558 if (finite(data[step * src->ds_cnt])) {
4559 if (data[step * src->ds_cnt] > dst->vf.val) {
4560 dst->vf.val = data[step * src->ds_cnt];
4561 dst->vf.when = src->start + (step + 1) * src->step;
4572 double average = 0.0;
4574 for (step = 0; step < steps; step++) {
4575 if (finite(data[step * src->ds_cnt])) {
4576 sum += data[step * src->ds_cnt];
4581 if (dst->vf.op == VDEF_TOTAL) {
4582 dst->vf.val = sum * src->step;
4583 dst->vf.when = 0; /* no time component */
4584 } else if (dst->vf.op == VDEF_AVERAGE) {
4585 dst->vf.val = sum / cnt;
4586 dst->vf.when = 0; /* no time component */
4588 average = sum / cnt;
4590 for (step = 0; step < steps; step++) {
4591 if (finite(data[step * src->ds_cnt])) {
4592 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4595 dst->vf.val = pow(sum / cnt, 0.5);
4596 dst->vf.when = 0; /* no time component */
4606 while (step != steps && isnan(data[step * src->ds_cnt]))
4608 if (step == steps) {
4612 dst->vf.val = data[step * src->ds_cnt];
4613 dst->vf.when = src->start + (step + 1) * src->step;
4615 while (step != steps) {
4616 if (finite(data[step * src->ds_cnt])) {
4617 if (data[step * src->ds_cnt] < dst->vf.val) {
4618 dst->vf.val = data[step * src->ds_cnt];
4619 dst->vf.when = src->start + (step + 1) * src->step;
4626 /* The time value returned here is one step before the
4627 * actual time value. This is the start of the first
4631 while (step != steps && isnan(data[step * src->ds_cnt]))
4633 if (step == steps) { /* all entries were NaN */
4637 dst->vf.val = data[step * src->ds_cnt];
4638 dst->vf.when = src->start + step * src->step;
4642 /* The time value returned here is the
4643 * actual time value. This is the end of the last
4647 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4649 if (step < 0) { /* all entries were NaN */
4653 dst->vf.val = data[step * src->ds_cnt];
4654 dst->vf.when = src->start + (step + 1) * src->step;
4659 case VDEF_LSLCORREL:{
4660 /* Bestfit line by linear least squares method */
4663 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4670 for (step = 0; step < steps; step++) {
4671 if (finite(data[step * src->ds_cnt])) {
4674 SUMxx += step * step;
4675 SUMxy += step * data[step * src->ds_cnt];
4676 SUMy += data[step * src->ds_cnt];
4677 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4681 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4682 y_intercept = (SUMy - slope * SUMx) / cnt;
4685 (SUMx * SUMy) / cnt) /
4687 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4689 if (dst->vf.op == VDEF_LSLSLOPE) {
4690 dst->vf.val = slope;
4692 } else if (dst->vf.op == VDEF_LSLINT) {
4693 dst->vf.val = y_intercept;
4695 } else if (dst->vf.op == VDEF_LSLCORREL) {
4696 dst->vf.val = correl;
4709 /* NaN < -INF < finite_values < INF */
4710 int vdef_percent_compar(
4716 /* Equality is not returned; this doesn't hurt except
4717 * (maybe) for a little performance.
4720 /* First catch NaN values. They are smallest */
4721 if (isnan(*(double *) a))
4723 if (isnan(*(double *) b))
4725 /* NaN doesn't reach this part so INF and -INF are extremes.
4726 * The sign from isinf() is compatible with the sign we return
4728 if (isinf(*(double *) a))
4729 return isinf(*(double *) a);
4730 if (isinf(*(double *) b))
4731 return isinf(*(double *) b);
4732 /* If we reach this, both values must be finite */
4733 if (*(double *) a < *(double *) b)
4742 enum info_type type,
4745 im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4746 if (im->grinfo == NULL) {
4747 im->grinfo = im->grinfo_current;