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;
2717 if (im->extra_flags & ONLY_GRAPH) {
2719 im->ximg = im->xsize;
2720 im->yimg = im->ysize;
2721 im->yorigin = im->ysize;
2726 /** +---+--------------------------------------------+
2727 ** | y |...............graph title..................|
2728 ** | +---+-------------------------------+--------+
2731 ** | i | a | | pie |
2732 ** | s | x | main graph area | chart |
2737 ** | l | b +-------------------------------+--------+
2738 ** | e | l | x axis labels | |
2739 ** +---+---+-------------------------------+--------+
2740 ** |....................legends.....................|
2741 ** +------------------------------------------------+
2743 ** +------------------------------------------------+
2746 if (im->ylegend[0] != '\0') {
2747 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2750 if (im->title[0] != '\0') {
2751 /* The title is placed "inbetween" two text lines so it
2752 ** automatically has some vertical spacing. The horizontal
2753 ** spacing is added here, on each side.
2755 /* if necessary, reduce the font size of the title until it fits the image width */
2756 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2760 if (im->draw_x_grid) {
2761 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2763 if (im->draw_y_grid || im->forceleftspace) {
2765 gfx_get_text_width(im, 0,
2773 size, im->tabwidth, "0") * im->unitslength;
2777 if (im->extra_flags & FULL_SIZE_MODE) {
2778 /* The actual size of the image to draw has been determined by the user.
2779 ** The graph area is the space remaining after accounting for the legend,
2780 ** the watermark, the pie chart, the axis labels, and the title.
2783 im->ximg = im->xsize;
2784 im->yimg = im->ysize;
2785 im->yorigin = im->ysize;
2788 im->yorigin += Ytitle;
2789 /* Now calculate the total size. Insert some spacing where
2790 desired. im->xorigin and im->yorigin need to correspond
2791 with the lower left corner of the main graph area or, if
2792 this one is not set, the imaginary box surrounding the
2794 /* Initial size calculation for the main graph area */
2795 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2797 Xmain -= Xspacing; /* put space between main graph area and right edge */
2798 im->xorigin = Xspacing + Xylabel;
2799 /* the length of the title should not influence with width of the graph
2800 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2801 if (Xvertical) { /* unit description */
2803 im->xorigin += Xvertical;
2807 /* The vertical size of the image is known in advance. The main graph area
2808 ** (Ymain) and im->yorigin must be set according to the space requirements
2809 ** of the legend and the axis labels.
2811 if (im->extra_flags & NOLEGEND) {
2812 /* set dimensions correctly if using full size mode with no legend */
2815 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2816 Ymain = im->yorigin;
2818 /* Determine where to place the legends onto the image.
2819 ** Set Ymain and adjust im->yorigin to match the space requirements.
2821 if (leg_place(im, &Ymain) == -1)
2826 /* remove title space *or* some padding above the graph from the main graph area */
2830 Ymain -= 1.5 * Yspacing;
2833 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2834 if (im->watermark[0] != '\0') {
2835 Ymain -= Ywatermark;
2839 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2841 /* The actual size of the image to draw is determined from
2842 ** several sources. The size given on the command line is
2843 ** the graph area but we need more as we have to draw labels
2844 ** and other things outside the graph area.
2847 if (im->ylegend[0] != '\0') {
2848 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2852 if (im->title[0] != '\0') {
2853 /* The title is placed "inbetween" two text lines so it
2854 ** automatically has some vertical spacing. The horizontal
2855 ** spacing is added here, on each side.
2857 /* don't care for the with of the title
2858 Xtitle = gfx_get_text_width(im->canvas, 0,
2859 im->text_prop[TEXT_PROP_TITLE].font,
2860 im->text_prop[TEXT_PROP_TITLE].size,
2862 im->title, 0) + 2*Xspacing; */
2863 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2870 /* Now calculate the total size. Insert some spacing where
2871 desired. im->xorigin and im->yorigin need to correspond
2872 with the lower left corner of the main graph area or, if
2873 this one is not set, the imaginary box surrounding the
2876 /* The legend width cannot yet be determined, as a result we
2877 ** have problems adjusting the image to it. For now, we just
2878 ** forget about it at all; the legend will have to fit in the
2879 ** size already allocated.
2881 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2883 im->ximg += Xspacing;
2884 im->xorigin = Xspacing + Xylabel;
2885 /* the length of the title should not influence with width of the graph
2886 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2887 if (Xvertical) { /* unit description */
2888 im->ximg += Xvertical;
2889 im->xorigin += Xvertical;
2892 /* The vertical size is interesting... we need to compare
2893 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2894 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2895 ** in order to start even thinking about Ylegend or Ywatermark.
2897 ** Do it in three portions: First calculate the inner part,
2898 ** then do the legend, then adjust the total height of the img,
2899 ** adding space for a watermark if one exists;
2901 /* reserve space for main and/or pie */
2902 im->yimg = Ymain + Yxlabel;
2903 im->yorigin = im->yimg - Yxlabel;
2904 /* reserve space for the title *or* some padding above the graph */
2907 im->yorigin += Ytitle;
2909 im->yimg += 1.5 * Yspacing;
2910 im->yorigin += 1.5 * Yspacing;
2912 /* reserve space for padding below the graph */
2913 im->yimg += Yspacing;
2914 /* Determine where to place the legends onto the image.
2915 ** Adjust im->yimg to match the space requirements.
2917 if (leg_place(im, 0) == -1)
2919 if (im->watermark[0] != '\0') {
2920 im->yimg += Ywatermark;
2928 static cairo_status_t cairo_output(
2932 unsigned int length)
2934 image_desc_t *im = closure;
2936 im->rendered_image =
2937 realloc(im->rendered_image, im->rendered_image_size + length);
2938 if (im->rendered_image == NULL)
2939 return CAIRO_STATUS_WRITE_ERROR;
2940 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2941 im->rendered_image_size += length;
2942 return CAIRO_STATUS_SUCCESS;
2945 /* draw that picture thing ... */
2950 int lazy = lazy_check(im);
2951 double areazero = 0.0;
2952 graph_desc_t *lastgdes = NULL;
2954 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2956 /* if we are lazy and there is nothing to PRINT ... quit now */
2957 if (lazy && im->prt_c == 0)
2959 /* pull the data from the rrd files ... */
2960 if (data_fetch(im) == -1)
2962 /* evaluate VDEF and CDEF operations ... */
2963 if (data_calc(im) == -1)
2965 /* calculate and PRINT and GPRINT definitions. We have to do it at
2966 * this point because it will affect the length of the legends
2967 * if there are no graph elements we stop here ...
2968 * if we are lazy, try to quit ...
2973 if ((i == 0) || lazy)
2975 /**************************************************************
2976 *** Calculating sizes and locations became a bit confusing ***
2977 *** so I moved this into a separate function. ***
2978 **************************************************************/
2979 if (graph_size_location(im, i) == -1)
2982 info.u_cnt = im->xorigin;
2983 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2984 info.u_cnt = im->yorigin - im->ysize;
2985 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2986 info.u_cnt = im->xsize;
2987 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2988 info.u_cnt = im->ysize;
2989 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2990 info.u_cnt = im->ximg;
2991 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2992 info.u_cnt = im->yimg;
2993 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2995 /* get actual drawing data and find min and max values */
2996 if (data_proc(im) == -1)
2998 if (!im->logarithmic) {
3002 /* identify si magnitude Kilo, Mega Giga ? */
3003 if (!im->rigid && !im->logarithmic)
3004 expand_range(im); /* make sure the upper and lower limit are
3007 info.u_val = im->minval;
3008 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3009 info.u_val = im->maxval;
3010 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3012 if (!calc_horizontal_grid(im))
3017 apply_gridfit(im); */
3018 /* the actual graph is created by going through the individual
3019 graph elements and then drawing them */
3020 cairo_surface_destroy(im->surface);
3021 switch (im->imgformat) {
3024 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3025 im->ximg * im->zoom,
3026 im->yimg * im->zoom);
3030 im->surface = strlen(im->graphfile)
3032 cairo_pdf_surface_create_for_stream
3033 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3034 : cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3035 im->yimg * im->zoom);
3039 im->surface = strlen(im->graphfile)
3041 cairo_ps_surface_create_for_stream
3042 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3043 : cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3044 im->yimg * im->zoom);
3048 im->surface = strlen(im->graphfile)
3050 cairo_svg_surface_create_for_stream
3051 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3052 : cairo_svg_surface_create(im->
3055 ximg * im->zoom, im->yimg * im->zoom);
3056 cairo_svg_surface_restrict_to_version
3057 (im->surface, CAIRO_SVG_VERSION_1_1);
3060 im->cr = cairo_create(im->surface);
3061 cairo_set_antialias(im->cr, im->graph_antialias);
3062 cairo_scale(im->cr, im->zoom, im->zoom);
3063 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3064 gfx_new_area(im, 0, 0, 0, im->yimg,
3065 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3066 gfx_add_point(im, im->ximg, 0);
3068 gfx_new_area(im, im->xorigin,
3071 im->xsize, im->yorigin,
3074 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3075 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3077 if (im->minval > 0.0)
3078 areazero = im->minval;
3079 if (im->maxval < 0.0)
3080 areazero = im->maxval;
3081 for (i = 0; i < im->gdes_c; i++) {
3082 switch (im->gdes[i].gf) {
3096 for (ii = 0; ii < im->xsize; ii++) {
3097 if (!isnan(im->gdes[i].p_data[ii])
3098 && im->gdes[i].p_data[ii] != 0.0) {
3099 if (im->gdes[i].yrule > 0) {
3106 im->ysize, 1.0, im->gdes[i].col);
3107 } else if (im->gdes[i].yrule < 0) {
3110 im->yorigin - im->ysize,
3115 im->ysize, 1.0, im->gdes[i].col);
3122 /* fix data points at oo and -oo */
3123 for (ii = 0; ii < im->xsize; ii++) {
3124 if (isinf(im->gdes[i].p_data[ii])) {
3125 if (im->gdes[i].p_data[ii] > 0) {
3126 im->gdes[i].p_data[ii] = im->maxval;
3128 im->gdes[i].p_data[ii] = im->minval;
3134 /* *******************************************************
3139 -------|--t-1--t--------------------------------
3141 if we know the value at time t was a then
3142 we draw a square from t-1 to t with the value a.
3144 ********************************************************* */
3145 if (im->gdes[i].col.alpha != 0.0) {
3146 /* GF_LINE and friend */
3147 if (im->gdes[i].gf == GF_LINE) {
3148 double last_y = 0.0;
3152 cairo_new_path(im->cr);
3153 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3154 if (im->gdes[i].dash) {
3155 cairo_set_dash(im->cr,
3156 im->gdes[i].p_dashes,
3157 im->gdes[i].ndash, im->gdes[i].offset);
3160 for (ii = 1; ii < im->xsize; ii++) {
3161 if (isnan(im->gdes[i].p_data[ii])
3162 || (im->slopemode == 1
3163 && isnan(im->gdes[i].p_data[ii - 1]))) {
3168 last_y = ytr(im, im->gdes[i].p_data[ii]);
3169 if (im->slopemode == 0) {
3170 double x = ii - 1 + im->xorigin;
3173 gfx_line_fit(im, &x, &y);
3174 cairo_move_to(im->cr, x, y);
3175 x = ii + im->xorigin;
3177 gfx_line_fit(im, &x, &y);
3178 cairo_line_to(im->cr, x, y);
3180 double x = ii - 1 + im->xorigin;
3182 ytr(im, im->gdes[i].p_data[ii - 1]);
3183 gfx_line_fit(im, &x, &y);
3184 cairo_move_to(im->cr, x, y);
3185 x = ii + im->xorigin;
3187 gfx_line_fit(im, &x, &y);
3188 cairo_line_to(im->cr, x, y);
3192 double x1 = ii + im->xorigin;
3193 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3195 if (im->slopemode == 0
3196 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3197 double x = ii - 1 + im->xorigin;
3200 gfx_line_fit(im, &x, &y);
3201 cairo_line_to(im->cr, x, y);
3204 gfx_line_fit(im, &x1, &y1);
3205 cairo_line_to(im->cr, x1, y1);
3208 cairo_set_source_rgba(im->cr,
3214 col.blue, im->gdes[i].col.alpha);
3215 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3216 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3217 cairo_stroke(im->cr);
3218 cairo_restore(im->cr);
3222 (double *) malloc(sizeof(double) * im->xsize * 2);
3224 (double *) malloc(sizeof(double) * im->xsize * 2);
3226 (double *) malloc(sizeof(double) * im->xsize * 2);
3228 (double *) malloc(sizeof(double) * im->xsize * 2);
3231 for (ii = 0; ii <= im->xsize; ii++) {
3234 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3240 AlmostEqual2sComplement(foreY
3244 AlmostEqual2sComplement(foreY
3254 foreY[cntI], im->gdes[i].col);
3255 while (cntI < idxI) {
3260 AlmostEqual2sComplement(foreY
3264 AlmostEqual2sComplement(foreY
3271 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3273 gfx_add_point(im, backX[idxI], backY[idxI]);
3279 AlmostEqual2sComplement(backY
3283 AlmostEqual2sComplement(backY
3290 gfx_add_point(im, backX[idxI], backY[idxI]);
3300 if (ii == im->xsize)
3302 if (im->slopemode == 0 && ii == 0) {
3305 if (isnan(im->gdes[i].p_data[ii])) {
3309 ytop = ytr(im, im->gdes[i].p_data[ii]);
3310 if (lastgdes && im->gdes[i].stack) {
3311 ybase = ytr(im, lastgdes->p_data[ii]);
3313 ybase = ytr(im, areazero);
3315 if (ybase == ytop) {
3321 double extra = ytop;
3326 if (im->slopemode == 0) {
3327 backY[++idxI] = ybase - 0.2;
3328 backX[idxI] = ii + im->xorigin - 1;
3329 foreY[idxI] = ytop + 0.2;
3330 foreX[idxI] = ii + im->xorigin - 1;
3332 backY[++idxI] = ybase - 0.2;
3333 backX[idxI] = ii + im->xorigin;
3334 foreY[idxI] = ytop + 0.2;
3335 foreX[idxI] = ii + im->xorigin;
3337 /* close up any remaining area */
3342 } /* else GF_LINE */
3344 /* if color != 0x0 */
3345 /* make sure we do not run into trouble when stacking on NaN */
3346 for (ii = 0; ii < im->xsize; ii++) {
3347 if (isnan(im->gdes[i].p_data[ii])) {
3348 if (lastgdes && (im->gdes[i].stack)) {
3349 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3351 im->gdes[i].p_data[ii] = areazero;
3355 lastgdes = &(im->gdes[i]);
3359 ("STACK should already be turned into LINE or AREA here");
3365 /* grid_paint also does the text */
3366 if (!(im->extra_flags & ONLY_GRAPH))
3368 if (!(im->extra_flags & ONLY_GRAPH))
3370 /* the RULES are the last thing to paint ... */
3371 for (i = 0; i < im->gdes_c; i++) {
3373 switch (im->gdes[i].gf) {
3375 if (im->gdes[i].yrule >= im->minval
3376 && im->gdes[i].yrule <= im->maxval) {
3378 if (im->gdes[i].dash) {
3379 cairo_set_dash(im->cr,
3380 im->gdes[i].p_dashes,
3381 im->gdes[i].ndash, im->gdes[i].offset);
3383 gfx_line(im, im->xorigin,
3384 ytr(im, im->gdes[i].yrule),
3385 im->xorigin + im->xsize,
3386 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3387 cairo_stroke(im->cr);
3388 cairo_restore(im->cr);
3392 if (im->gdes[i].xrule >= im->start
3393 && im->gdes[i].xrule <= im->end) {
3395 if (im->gdes[i].dash) {
3396 cairo_set_dash(im->cr,
3397 im->gdes[i].p_dashes,
3398 im->gdes[i].ndash, im->gdes[i].offset);
3401 xtr(im, im->gdes[i].xrule),
3402 im->yorigin, xtr(im,
3406 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3407 cairo_stroke(im->cr);
3408 cairo_restore(im->cr);
3417 switch (im->imgformat) {
3420 cairo_status_t status;
3422 status = strlen(im->graphfile) ?
3423 cairo_surface_write_to_png(im->surface, im->graphfile)
3424 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3427 if (status != CAIRO_STATUS_SUCCESS) {
3428 rrd_set_error("Could not save png to '%s'", im->graphfile);
3434 if (strlen(im->graphfile)) {
3435 cairo_show_page(im->cr);
3437 cairo_surface_finish(im->surface);
3446 /*****************************************************
3448 *****************************************************/
3455 if ((im->gdes = (graph_desc_t *)
3456 rrd_realloc(im->gdes, (im->gdes_c)
3457 * sizeof(graph_desc_t))) == NULL) {
3458 rrd_set_error("realloc graph_descs");
3463 im->gdes[im->gdes_c - 1].step = im->step;
3464 im->gdes[im->gdes_c - 1].step_orig = im->step;
3465 im->gdes[im->gdes_c - 1].stack = 0;
3466 im->gdes[im->gdes_c - 1].linewidth = 0;
3467 im->gdes[im->gdes_c - 1].debug = 0;
3468 im->gdes[im->gdes_c - 1].start = im->start;
3469 im->gdes[im->gdes_c - 1].start_orig = im->start;
3470 im->gdes[im->gdes_c - 1].end = im->end;
3471 im->gdes[im->gdes_c - 1].end_orig = im->end;
3472 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3473 im->gdes[im->gdes_c - 1].data = NULL;
3474 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3475 im->gdes[im->gdes_c - 1].data_first = 0;
3476 im->gdes[im->gdes_c - 1].p_data = NULL;
3477 im->gdes[im->gdes_c - 1].rpnp = NULL;
3478 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3479 im->gdes[im->gdes_c - 1].shift = 0.0;
3480 im->gdes[im->gdes_c - 1].dash = 0;
3481 im->gdes[im->gdes_c - 1].ndash = 0;
3482 im->gdes[im->gdes_c - 1].offset = 0;
3483 im->gdes[im->gdes_c - 1].col.red = 0.0;
3484 im->gdes[im->gdes_c - 1].col.green = 0.0;
3485 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3486 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3487 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3488 im->gdes[im->gdes_c - 1].format[0] = '\0';
3489 im->gdes[im->gdes_c - 1].strftm = 0;
3490 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3491 im->gdes[im->gdes_c - 1].ds = -1;
3492 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3493 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3494 im->gdes[im->gdes_c - 1].yrule = DNAN;
3495 im->gdes[im->gdes_c - 1].xrule = 0;
3499 /* copies input untill the first unescaped colon is found
3500 or until input ends. backslashes have to be escaped as well */
3502 const char *const input,
3508 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3509 if (input[inp] == '\\'
3510 && input[inp + 1] != '\0'
3511 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3512 output[outp++] = input[++inp];
3514 output[outp++] = input[inp];
3517 output[outp] = '\0';
3521 /* Now just a wrapper around rrd_graph_v */
3533 info_t *grinfo = NULL;
3536 grinfo = rrd_graph_v(argc, argv);
3542 if (strcmp(walker->key, "image_info") == 0) {
3545 rrd_realloc((*prdata),
3546 (prlines + 1) * sizeof(char *))) == NULL) {
3547 rrd_set_error("realloc prdata");
3550 /* imginfo goes to position 0 in the prdata array */
3551 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3552 + 2) * sizeof(char));
3553 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3554 (*prdata)[prlines] = NULL;
3556 /* skip anything else */
3557 walker = walker->next;
3561 if (strcmp(walker->key, "image_width") == 0) {
3562 *xsize = walker->value.u_int;
3563 } else if (strcmp(walker->key, "image_height") == 0) {
3564 *ysize = walker->value.u_int;
3565 } else if (strcmp(walker->key, "value_min") == 0) {
3566 *ymin = walker->value.u_val;
3567 } else if (strcmp(walker->key, "value_max") == 0) {
3568 *ymax = walker->value.u_val;
3569 } else if (strncmp(walker->key, "print", 6) == 0) { /* keys are prdate[0..] */
3572 rrd_realloc((*prdata),
3573 (prlines + 1) * sizeof(char *))) == NULL) {
3574 rrd_set_error("realloc prdata");
3577 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3578 + 2) * sizeof(char));
3579 (*prdata)[prlines] = NULL;
3580 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3581 } else if (strcmp(walker->key, "image") == 0) {
3582 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3583 (stream ? stream : stdout));
3585 /* skip anything else */
3586 walker = walker->next;
3593 /* Some surgery done on this function, it became ridiculously big.
3595 ** - initializing now in rrd_graph_init()
3596 ** - options parsing now in rrd_graph_options()
3597 ** - script parsing now in rrd_graph_script()
3599 info_t *rrd_graph_v(
3606 rrd_graph_init(&im);
3607 /* a dummy surface so that we can measure text sizes for placements */
3608 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3609 im.cr = cairo_create(im.surface);
3610 rrd_graph_options(argc, argv, &im);
3611 if (rrd_test_error()) {
3612 info_free(im.grinfo);
3617 if (optind >= argc) {
3618 info_free(im.grinfo);
3620 rrd_set_error("missing filename");
3624 if (strlen(argv[optind]) >= MAXPATH) {
3625 rrd_set_error("filename (including path) too long");
3626 info_free(im.grinfo);
3631 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3632 im.graphfile[MAXPATH - 1] = '\0';
3634 if (strcmp(im.graphfile, "-") == 0) {
3635 im.graphfile[0] = '\0';
3638 rrd_graph_script(argc, argv, &im, 1);
3639 if (rrd_test_error()) {
3640 info_free(im.grinfo);
3645 /* Everything is now read and the actual work can start */
3647 if (graph_paint(&im) == -1) {
3648 info_free(im.grinfo);
3654 /* The image is generated and needs to be output.
3655 ** Also, if needed, print a line with information about the image.
3662 sprintf_alloc(im.imginfo,
3665 im.ximg), (long) (im.zoom * im.yimg));
3666 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3669 if (im.rendered_image) {
3672 img.u_blo.size = im.rendered_image_size;
3673 img.u_blo.ptr = im.rendered_image;
3674 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3681 void rrd_graph_init(
3690 #ifdef HAVE_SETLOCALE
3691 setlocale(LC_TIME, "");
3692 #ifdef HAVE_MBSTOWCS
3693 setlocale(LC_CTYPE, "");
3698 im->draw_x_grid = 1;
3699 im->draw_y_grid = 1;
3700 im->extra_flags = 0;
3701 im->font_options = cairo_font_options_create();
3702 im->forceleftspace = 0;
3705 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3706 im->grid_dash_off = 1;
3707 im->grid_dash_on = 1;
3709 im->grinfo = (info_t *) NULL;
3710 im->grinfo_current = (info_t *) NULL;
3711 im->imgformat = IF_PNG;
3714 im->logarithmic = 0;
3720 im->rendered_image_size = 0;
3721 im->rendered_image = NULL;
3726 im->tabwidth = 40.0;
3727 im->title[0] = '\0';
3728 im->unitsexponent = 9999;
3729 im->unitslength = 6;
3730 im->viewfactor = 1.0;
3731 im->watermark[0] = '\0';
3733 im->xlab_user.minsec = -1;
3736 im->ygridstep = DNAN;
3738 im->ylegend[0] = '\0';
3742 cairo_font_options_set_hint_style
3743 (im->font_options, CAIRO_HINT_STYLE_FULL);
3744 cairo_font_options_set_hint_metrics
3745 (im->font_options, CAIRO_HINT_METRICS_ON);
3746 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3747 for (i = 0; i < DIM(graph_col); i++)
3748 im->graph_col[i] = graph_col[i];
3749 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3752 char rrd_win_default_font[1000];
3754 windir = getenv("windir");
3755 /* %windir% is something like D:\windows or C:\winnt */
3756 if (windir != NULL) {
3757 strncpy(rrd_win_default_font, windir, 500);
3758 rrd_win_default_font[500] = '\0';
3759 strcat(rrd_win_default_font, "\\fonts\\");
3760 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3761 for (i = 0; i < DIM(text_prop); i++) {
3762 strncpy(text_prop[i].font,
3763 rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3764 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3772 deffont = getenv("RRD_DEFAULT_FONT");
3773 if (deffont != NULL) {
3774 for (i = 0; i < DIM(text_prop); i++) {
3775 strncpy(text_prop[i].font, deffont,
3776 sizeof(text_prop[i].font) - 1);
3777 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3781 for (i = 0; i < DIM(text_prop); i++) {
3782 im->text_prop[i].size = text_prop[i].size;
3783 strcpy(im->text_prop[i].font, text_prop[i].font);
3787 void rrd_graph_options(
3794 char *parsetime_error = NULL;
3795 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3796 time_t start_tmp = 0, end_tmp = 0;
3798 struct rrd_time_value start_tv, end_tv;
3799 long unsigned int color;
3800 char *old_locale = "";
3802 /* defines for long options without a short equivalent. should be bytes,
3803 and may not collide with (the ASCII value of) short options */
3804 #define LONGOPT_UNITS_SI 255
3805 struct option long_options[] = {
3807 "start", required_argument, 0, 's'}, {
3808 "end", required_argument, 0,
3811 required_argument, 0,
3825 "height", required_argument, 0, 'h'}, {
3826 "full-size-mode", no_argument,
3844 "base", required_argument, 0, 'b'}, {
3845 "logarithmic", no_argument, 0,
3848 required_argument, 0,
3857 "imginfo", required_argument, 0, 'f'}, {
3859 required_argument, 0, 'a'}, {
3865 "zoom", required_argument, 0, 'm'}, {
3866 "no-legend", no_argument, 0,
3868 "force-rules-legend",
3878 "no-minor", no_argument, 0, 'I'}, {
3879 "slope-mode", no_argument, 0,
3882 no_argument, 0, 'A'}, {
3883 "alt-autoscale-min",
3887 "alt-autoscale-max",
3896 "units-exponent", required_argument,
3898 "units-length", required_argument,
3900 "units", required_argument, 0,
3901 LONGOPT_UNITS_SI}, {
3902 "step", required_argument, 0,
3905 required_argument, 0,
3910 "graph-render-mode",
3915 "font-smoothing-threshold",
3916 required_argument, 0, 'B'}, {
3917 "watermark", required_argument, 0, 'W'}, {
3918 "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 */
3923 opterr = 0; /* initialize getopt */
3924 parsetime("end-24h", &start_tv);
3925 parsetime("now", &end_tv);
3927 int option_index = 0;
3929 int col_start, col_end;
3931 opt = getopt_long(argc, argv,
3932 "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",
3933 long_options, &option_index);
3938 im->extra_flags |= NOMINOR;
3941 im->extra_flags |= ALTYGRID;
3944 im->extra_flags |= ALTAUTOSCALE;
3947 im->extra_flags |= ALTAUTOSCALE_MIN;
3950 im->extra_flags |= ALTAUTOSCALE_MAX;
3953 im->extra_flags |= ONLY_GRAPH;
3956 im->extra_flags |= NOLEGEND;
3959 im->extra_flags |= FORCE_RULES_LEGEND;
3961 case LONGOPT_UNITS_SI:
3962 if (im->extra_flags & FORCE_UNITS) {
3963 rrd_set_error("--units can only be used once!");
3964 setlocale(LC_NUMERIC, old_locale);
3967 if (strcmp(optarg, "si") == 0)
3968 im->extra_flags |= FORCE_UNITS_SI;
3970 rrd_set_error("invalid argument for --units: %s", optarg);
3975 im->unitsexponent = atoi(optarg);
3978 im->unitslength = atoi(optarg);
3979 im->forceleftspace = 1;
3982 old_locale = setlocale(LC_NUMERIC, "C");
3983 im->tabwidth = atof(optarg);
3984 setlocale(LC_NUMERIC, old_locale);
3987 old_locale = setlocale(LC_NUMERIC, "C");
3988 im->step = atoi(optarg);
3989 setlocale(LC_NUMERIC, old_locale);
3995 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3996 rrd_set_error("start time: %s", parsetime_error);
4001 if ((parsetime_error = parsetime(optarg, &end_tv))) {
4002 rrd_set_error("end time: %s", parsetime_error);
4007 if (strcmp(optarg, "none") == 0) {
4008 im->draw_x_grid = 0;
4012 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4014 &im->xlab_user.gridst,
4016 &im->xlab_user.mgridst,
4018 &im->xlab_user.labst,
4019 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4020 strncpy(im->xlab_form, optarg + stroff,
4021 sizeof(im->xlab_form) - 1);
4022 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4024 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4025 rrd_set_error("unknown keyword %s", scan_gtm);
4028 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4030 rrd_set_error("unknown keyword %s", scan_mtm);
4033 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4034 rrd_set_error("unknown keyword %s", scan_ltm);
4037 im->xlab_user.minsec = 1;
4038 im->xlab_user.stst = im->xlab_form;
4040 rrd_set_error("invalid x-grid format");
4046 if (strcmp(optarg, "none") == 0) {
4047 im->draw_y_grid = 0;
4050 old_locale = setlocale(LC_NUMERIC, "C");
4051 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4052 setlocale(LC_NUMERIC, old_locale);
4053 if (im->ygridstep <= 0) {
4054 rrd_set_error("grid step must be > 0");
4056 } else if (im->ylabfact < 1) {
4057 rrd_set_error("label factor must be > 0");
4061 setlocale(LC_NUMERIC, old_locale);
4062 rrd_set_error("invalid y-grid format");
4067 strncpy(im->ylegend, optarg, 150);
4068 im->ylegend[150] = '\0';
4071 old_locale = setlocale(LC_NUMERIC, "C");
4072 im->maxval = atof(optarg);
4073 setlocale(LC_NUMERIC, old_locale);
4076 old_locale = setlocale(LC_NUMERIC, "C");
4077 im->minval = atof(optarg);
4078 setlocale(LC_NUMERIC, old_locale);
4081 im->base = atol(optarg);
4082 if (im->base != 1024 && im->base != 1000) {
4084 ("the only sensible value for base apart from 1000 is 1024");
4089 long_tmp = atol(optarg);
4090 if (long_tmp < 10) {
4091 rrd_set_error("width below 10 pixels");
4094 im->xsize = long_tmp;
4097 long_tmp = atol(optarg);
4098 if (long_tmp < 10) {
4099 rrd_set_error("height below 10 pixels");
4102 im->ysize = long_tmp;
4105 im->extra_flags |= FULL_SIZE_MODE;
4108 /* interlaced png not supported at the moment */
4114 im->imginfo = optarg;
4118 (im->imgformat = if_conv(optarg)) == -1) {
4119 rrd_set_error("unsupported graphics format '%s'", optarg);
4130 im->logarithmic = 1;
4134 "%10[A-Z]#%n%8lx%n",
4135 col_nam, &col_start, &color, &col_end) == 2) {
4137 int col_len = col_end - col_start;
4142 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4150 (((color & 0xF000) *
4151 0x11000) | ((color & 0x0F00) *
4152 0x01100) | ((color &
4155 ((color & 0x000F) * 0x00011)
4159 color = (color << 8) + 0xff /* shift left by 8 */ ;
4164 rrd_set_error("the color format is #RRGGBB[AA]");
4167 if ((ci = grc_conv(col_nam)) != -1) {
4168 im->graph_col[ci] = gfx_hex_to_col(color);
4170 rrd_set_error("invalid color name '%s'", col_nam);
4174 rrd_set_error("invalid color def format");
4183 old_locale = setlocale(LC_NUMERIC, "C");
4184 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4185 int sindex, propidx;
4187 setlocale(LC_NUMERIC, old_locale);
4188 if ((sindex = text_prop_conv(prop)) != -1) {
4189 for (propidx = sindex;
4190 propidx < TEXT_PROP_LAST; propidx++) {
4192 im->text_prop[propidx].size = size;
4194 if ((int) strlen(prop) > end) {
4195 if (prop[end] == ':') {
4196 strncpy(im->text_prop[propidx].font,
4197 prop + end + 1, 255);
4198 im->text_prop[propidx].font[255] = '\0';
4201 ("expected after font size in '%s'",
4206 if (propidx == sindex && sindex != 0)
4210 rrd_set_error("invalid fonttag '%s'", prop);
4214 setlocale(LC_NUMERIC, old_locale);
4215 rrd_set_error("invalid text property format");
4221 old_locale = setlocale(LC_NUMERIC, "C");
4222 im->zoom = atof(optarg);
4223 setlocale(LC_NUMERIC, old_locale);
4224 if (im->zoom <= 0.0) {
4225 rrd_set_error("zoom factor must be > 0");
4230 strncpy(im->title, optarg, 150);
4231 im->title[150] = '\0';
4234 if (strcmp(optarg, "normal") == 0) {
4235 cairo_font_options_set_antialias
4236 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4237 cairo_font_options_set_hint_style
4238 (im->font_options, CAIRO_HINT_STYLE_FULL);
4239 } else if (strcmp(optarg, "light") == 0) {
4240 cairo_font_options_set_antialias
4241 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4242 cairo_font_options_set_hint_style
4243 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4244 } else if (strcmp(optarg, "mono") == 0) {
4245 cairo_font_options_set_antialias
4246 (im->font_options, CAIRO_ANTIALIAS_NONE);
4247 cairo_font_options_set_hint_style
4248 (im->font_options, CAIRO_HINT_STYLE_FULL);
4250 rrd_set_error("unknown font-render-mode '%s'", optarg);
4255 if (strcmp(optarg, "normal") == 0)
4256 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4257 else if (strcmp(optarg, "mono") == 0)
4258 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4260 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4265 /* not supported curently */
4268 strncpy(im->watermark, optarg, 100);
4269 im->watermark[99] = '\0';
4273 rrd_set_error("unknown option '%c'", optopt);
4275 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4280 if (im->logarithmic == 1 && im->minval <= 0) {
4282 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4286 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4287 /* error string is set in parsetime.c */
4291 if (start_tmp < 3600 * 24 * 365 * 10) {
4293 ("the first entry to fetch should be after 1980 (%ld)",
4298 if (end_tmp < start_tmp) {
4300 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4304 im->start = start_tmp;
4306 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4309 int rrd_graph_color(
4317 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4319 color = strstr(var, "#");
4320 if (color == NULL) {
4321 if (optional == 0) {
4322 rrd_set_error("Found no color in %s", err);
4329 long unsigned int col;
4331 rest = strstr(color, ":");
4338 sscanf(color, "#%6lx%n", &col, &n);
4339 col = (col << 8) + 0xff /* shift left by 8 */ ;
4341 rrd_set_error("Color problem in %s", err);
4344 sscanf(color, "#%8lx%n", &col, &n);
4348 rrd_set_error("Color problem in %s", err);
4350 if (rrd_test_error())
4352 gdp->col = gfx_hex_to_col(col);
4365 while (*ptr != '\0')
4366 if (*ptr++ == '%') {
4368 /* line cannot end with percent char */
4371 /* '%s', '%S' and '%%' are allowed */
4372 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4374 /* %c is allowed (but use only with vdef!) */
4375 else if (*ptr == 'c') {
4380 /* or else '% 6.2lf' and such are allowed */
4382 /* optional padding character */
4383 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4385 /* This should take care of 'm.n' with all three optional */
4386 while (*ptr >= '0' && *ptr <= '9')
4390 while (*ptr >= '0' && *ptr <= '9')
4392 /* Either 'le', 'lf' or 'lg' must follow here */
4395 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4410 const char *const str)
4412 /* A VDEF currently is either "func" or "param,func"
4413 * so the parsing is rather simple. Change if needed.
4421 old_locale = setlocale(LC_NUMERIC, "C");
4422 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4423 setlocale(LC_NUMERIC, old_locale);
4424 if (n == (int) strlen(str)) { /* matched */
4428 sscanf(str, "%29[A-Z]%n", func, &n);
4429 if (n == (int) strlen(str)) { /* matched */
4433 ("Unknown function string '%s' in VDEF '%s'",
4438 if (!strcmp("PERCENT", func))
4439 gdes->vf.op = VDEF_PERCENT;
4440 else if (!strcmp("MAXIMUM", func))
4441 gdes->vf.op = VDEF_MAXIMUM;
4442 else if (!strcmp("AVERAGE", func))
4443 gdes->vf.op = VDEF_AVERAGE;
4444 else if (!strcmp("STDEV", func))
4445 gdes->vf.op = VDEF_STDEV;
4446 else if (!strcmp("MINIMUM", func))
4447 gdes->vf.op = VDEF_MINIMUM;
4448 else if (!strcmp("TOTAL", func))
4449 gdes->vf.op = VDEF_TOTAL;
4450 else if (!strcmp("FIRST", func))
4451 gdes->vf.op = VDEF_FIRST;
4452 else if (!strcmp("LAST", func))
4453 gdes->vf.op = VDEF_LAST;
4454 else if (!strcmp("LSLSLOPE", func))
4455 gdes->vf.op = VDEF_LSLSLOPE;
4456 else if (!strcmp("LSLINT", func))
4457 gdes->vf.op = VDEF_LSLINT;
4458 else if (!strcmp("LSLCORREL", func))
4459 gdes->vf.op = VDEF_LSLCORREL;
4462 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4465 switch (gdes->vf.op) {
4467 if (isnan(param)) { /* no parameter given */
4469 ("Function '%s' needs parameter in VDEF '%s'\n",
4473 if (param >= 0.0 && param <= 100.0) {
4474 gdes->vf.param = param;
4475 gdes->vf.val = DNAN; /* undefined */
4476 gdes->vf.when = 0; /* undefined */
4479 ("Parameter '%f' out of range in VDEF '%s'\n",
4480 param, gdes->vname);
4493 case VDEF_LSLCORREL:
4495 gdes->vf.param = DNAN;
4496 gdes->vf.val = DNAN;
4500 ("Function '%s' needs no parameter in VDEF '%s'\n",
4514 graph_desc_t *src, *dst;
4518 dst = &im->gdes[gdi];
4519 src = &im->gdes[dst->vidx];
4520 data = src->data + src->ds;
4521 steps = (src->end - src->start) / src->step;
4524 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4525 src->start, src->end, steps);
4527 switch (dst->vf.op) {
4531 if ((array = malloc(steps * sizeof(double))) == NULL) {
4532 rrd_set_error("malloc VDEV_PERCENT");
4535 for (step = 0; step < steps; step++) {
4536 array[step] = data[step * src->ds_cnt];
4538 qsort(array, step, sizeof(double), vdef_percent_compar);
4539 field = (steps - 1) * dst->vf.param / 100;
4540 dst->vf.val = array[field];
4541 dst->vf.when = 0; /* no time component */
4544 for (step = 0; step < steps; step++)
4545 printf("DEBUG: %3li:%10.2f %c\n",
4546 step, array[step], step == field ? '*' : ' ');
4552 while (step != steps && isnan(data[step * src->ds_cnt]))
4554 if (step == steps) {
4558 dst->vf.val = data[step * src->ds_cnt];
4559 dst->vf.when = src->start + (step + 1) * src->step;
4561 while (step != steps) {
4562 if (finite(data[step * src->ds_cnt])) {
4563 if (data[step * src->ds_cnt] > dst->vf.val) {
4564 dst->vf.val = data[step * src->ds_cnt];
4565 dst->vf.when = src->start + (step + 1) * src->step;
4576 double average = 0.0;
4578 for (step = 0; step < steps; step++) {
4579 if (finite(data[step * src->ds_cnt])) {
4580 sum += data[step * src->ds_cnt];
4585 if (dst->vf.op == VDEF_TOTAL) {
4586 dst->vf.val = sum * src->step;
4587 dst->vf.when = 0; /* no time component */
4588 } else if (dst->vf.op == VDEF_AVERAGE) {
4589 dst->vf.val = sum / cnt;
4590 dst->vf.when = 0; /* no time component */
4592 average = sum / cnt;
4594 for (step = 0; step < steps; step++) {
4595 if (finite(data[step * src->ds_cnt])) {
4596 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4599 dst->vf.val = pow(sum / cnt, 0.5);
4600 dst->vf.when = 0; /* no time component */
4610 while (step != steps && isnan(data[step * src->ds_cnt]))
4612 if (step == steps) {
4616 dst->vf.val = data[step * src->ds_cnt];
4617 dst->vf.when = src->start + (step + 1) * src->step;
4619 while (step != steps) {
4620 if (finite(data[step * src->ds_cnt])) {
4621 if (data[step * src->ds_cnt] < dst->vf.val) {
4622 dst->vf.val = data[step * src->ds_cnt];
4623 dst->vf.when = src->start + (step + 1) * src->step;
4630 /* The time value returned here is one step before the
4631 * actual time value. This is the start of the first
4635 while (step != steps && isnan(data[step * src->ds_cnt]))
4637 if (step == steps) { /* all entries were NaN */
4641 dst->vf.val = data[step * src->ds_cnt];
4642 dst->vf.when = src->start + step * src->step;
4646 /* The time value returned here is the
4647 * actual time value. This is the end of the last
4651 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4653 if (step < 0) { /* all entries were NaN */
4657 dst->vf.val = data[step * src->ds_cnt];
4658 dst->vf.when = src->start + (step + 1) * src->step;
4663 case VDEF_LSLCORREL:{
4664 /* Bestfit line by linear least squares method */
4667 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4674 for (step = 0; step < steps; step++) {
4675 if (finite(data[step * src->ds_cnt])) {
4678 SUMxx += step * step;
4679 SUMxy += step * data[step * src->ds_cnt];
4680 SUMy += data[step * src->ds_cnt];
4681 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4685 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4686 y_intercept = (SUMy - slope * SUMx) / cnt;
4689 (SUMx * SUMy) / cnt) /
4691 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4693 if (dst->vf.op == VDEF_LSLSLOPE) {
4694 dst->vf.val = slope;
4696 } else if (dst->vf.op == VDEF_LSLINT) {
4697 dst->vf.val = y_intercept;
4699 } else if (dst->vf.op == VDEF_LSLCORREL) {
4700 dst->vf.val = correl;
4713 /* NaN < -INF < finite_values < INF */
4714 int vdef_percent_compar(
4720 /* Equality is not returned; this doesn't hurt except
4721 * (maybe) for a little performance.
4724 /* First catch NaN values. They are smallest */
4725 if (isnan(*(double *) a))
4727 if (isnan(*(double *) b))
4729 /* NaN doesn't reach this part so INF and -INF are extremes.
4730 * The sign from isinf() is compatible with the sign we return
4732 if (isinf(*(double *) a))
4733 return isinf(*(double *) a);
4734 if (isinf(*(double *) b))
4735 return isinf(*(double *) b);
4736 /* If we reach this, both values must be finite */
4737 if (*(double *) a < *(double *) b)
4746 enum info_type type,
4749 im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4750 if (im->grinfo == NULL) {
4751 im->grinfo = im->grinfo_current;