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 "DejaVuSansMono-Roman.ttf"
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);
341 cairo_surface_destroy(im->surface);
343 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
344 cairo_status_to_string(status));
349 /* find SI magnitude symbol for the given number*/
351 image_desc_t *im, /* image description */
357 char *symbol[] = { "a", /* 10e-18 Atto */
358 "f", /* 10e-15 Femto */
359 "p", /* 10e-12 Pico */
360 "n", /* 10e-9 Nano */
361 "u", /* 10e-6 Micro */
362 "m", /* 10e-3 Milli */
367 "T", /* 10e12 Tera */
368 "P", /* 10e15 Peta */
375 if (*value == 0.0 || isnan(*value)) {
379 sindex = floor(log(fabs(*value)) / log((double) im->base));
380 *magfact = pow((double) im->base, (double) sindex);
381 (*value) /= (*magfact);
383 if (sindex <= symbcenter && sindex >= -symbcenter) {
384 (*symb_ptr) = symbol[sindex + symbcenter];
391 static char si_symbol[] = {
392 'a', /* 10e-18 Atto */
393 'f', /* 10e-15 Femto */
394 'p', /* 10e-12 Pico */
395 'n', /* 10e-9 Nano */
396 'u', /* 10e-6 Micro */
397 'm', /* 10e-3 Milli */
402 'T', /* 10e12 Tera */
403 'P', /* 10e15 Peta */
406 static const int si_symbcenter = 6;
408 /* find SI magnitude symbol for the numbers on the y-axis*/
410 image_desc_t *im /* image description */
414 double digits, viewdigits = 0;
417 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
418 log((double) im->base));
420 if (im->unitsexponent != 9999) {
421 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
422 viewdigits = floor(im->unitsexponent / 3);
427 im->magfact = pow((double) im->base, digits);
430 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
433 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
435 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
436 ((viewdigits + si_symbcenter) >= 0))
437 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
442 /* move min and max values around to become sensible */
447 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
448 600.0, 500.0, 400.0, 300.0, 250.0,
449 200.0, 125.0, 100.0, 90.0, 80.0,
450 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
451 25.0, 20.0, 10.0, 9.0, 8.0,
452 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
453 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
454 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
457 double scaled_min, scaled_max;
464 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
465 im->minval, im->maxval, im->magfact);
468 if (isnan(im->ygridstep)) {
469 if (im->extra_flags & ALTAUTOSCALE) {
470 /* measure the amplitude of the function. Make sure that
471 graph boundaries are slightly higher then max/min vals
472 so we can see amplitude on the graph */
475 delt = im->maxval - im->minval;
477 fact = 2.0 * pow(10.0,
479 (max(fabs(im->minval), fabs(im->maxval)) /
482 adj = (fact - delt) * 0.55;
485 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
486 im->minval, im->maxval, delt, fact, adj);
491 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
492 /* measure the amplitude of the function. Make sure that
493 graph boundaries are slightly lower than min vals
494 so we can see amplitude on the graph */
495 adj = (im->maxval - im->minval) * 0.1;
497 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
498 /* measure the amplitude of the function. Make sure that
499 graph boundaries are slightly higher than max vals
500 so we can see amplitude on the graph */
501 adj = (im->maxval - im->minval) * 0.1;
504 scaled_min = im->minval / im->magfact;
505 scaled_max = im->maxval / im->magfact;
507 for (i = 1; sensiblevalues[i] > 0; i++) {
508 if (sensiblevalues[i - 1] >= scaled_min &&
509 sensiblevalues[i] <= scaled_min)
510 im->minval = sensiblevalues[i] * (im->magfact);
512 if (-sensiblevalues[i - 1] <= scaled_min &&
513 -sensiblevalues[i] >= scaled_min)
514 im->minval = -sensiblevalues[i - 1] * (im->magfact);
516 if (sensiblevalues[i - 1] >= scaled_max &&
517 sensiblevalues[i] <= scaled_max)
518 im->maxval = sensiblevalues[i - 1] * (im->magfact);
520 if (-sensiblevalues[i - 1] <= scaled_max &&
521 -sensiblevalues[i] >= scaled_max)
522 im->maxval = -sensiblevalues[i] * (im->magfact);
526 /* adjust min and max to the grid definition if there is one */
527 im->minval = (double) im->ylabfact * im->ygridstep *
528 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
529 im->maxval = (double) im->ylabfact * im->ygridstep *
530 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
534 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
535 im->minval, im->maxval, im->magfact);
543 if (isnan(im->minval) || isnan(im->maxval))
546 if (im->logarithmic) {
547 double ya, yb, ypix, ypixfrac;
548 double log10_range = log10(im->maxval) - log10(im->minval);
550 ya = pow((double) 10, floor(log10(im->minval)));
551 while (ya < im->minval)
554 return; /* don't have y=10^x gridline */
556 if (yb <= im->maxval) {
557 /* we have at least 2 y=10^x gridlines.
558 Make sure distance between them in pixels
559 are an integer by expanding im->maxval */
560 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
561 double factor = y_pixel_delta / floor(y_pixel_delta);
562 double new_log10_range = factor * log10_range;
563 double new_ymax_log10 = log10(im->minval) + new_log10_range;
565 im->maxval = pow(10, new_ymax_log10);
566 ytr(im, DNAN); /* reset precalc */
567 log10_range = log10(im->maxval) - log10(im->minval);
569 /* make sure first y=10^x gridline is located on
570 integer pixel position by moving scale slightly
571 downwards (sub-pixel movement) */
572 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
573 ypixfrac = ypix - floor(ypix);
574 if (ypixfrac > 0 && ypixfrac < 1) {
575 double yfrac = ypixfrac / im->ysize;
577 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
578 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
579 ytr(im, DNAN); /* reset precalc */
582 /* Make sure we have an integer pixel distance between
583 each minor gridline */
584 double ypos1 = ytr(im, im->minval);
585 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
586 double y_pixel_delta = ypos1 - ypos2;
587 double factor = y_pixel_delta / floor(y_pixel_delta);
588 double new_range = factor * (im->maxval - im->minval);
589 double gridstep = im->ygrid_scale.gridstep;
590 double minor_y, minor_y_px, minor_y_px_frac;
592 if (im->maxval > 0.0)
593 im->maxval = im->minval + new_range;
595 im->minval = im->maxval - new_range;
596 ytr(im, DNAN); /* reset precalc */
597 /* make sure first minor gridline is on integer pixel y coord */
598 minor_y = gridstep * floor(im->minval / gridstep);
599 while (minor_y < im->minval)
601 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
602 minor_y_px_frac = minor_y_px - floor(minor_y_px);
603 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
604 double yfrac = minor_y_px_frac / im->ysize;
605 double range = im->maxval - im->minval;
607 im->minval = im->minval - yfrac * range;
608 im->maxval = im->maxval - yfrac * range;
609 ytr(im, DNAN); /* reset precalc */
611 calc_horizontal_grid(im); /* recalc with changed im->maxval */
615 /* reduce data reimplementation by Alex */
618 enum cf_en cf, /* which consolidation function ? */
619 unsigned long cur_step, /* step the data currently is in */
620 time_t *start, /* start, end and step as requested ... */
621 time_t *end, /* ... by the application will be ... */
622 unsigned long *step, /* ... adjusted to represent reality */
623 unsigned long *ds_cnt, /* number of data sources in file */
625 { /* two dimensional array containing the data */
626 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
627 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
629 rrd_value_t *srcptr, *dstptr;
631 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
634 row_cnt = ((*end) - (*start)) / cur_step;
640 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
641 row_cnt, reduce_factor, *start, *end, cur_step);
642 for (col = 0; col < row_cnt; col++) {
643 printf("time %10lu: ", *start + (col + 1) * cur_step);
644 for (i = 0; i < *ds_cnt; i++)
645 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
650 /* We have to combine [reduce_factor] rows of the source
651 ** into one row for the destination. Doing this we also
652 ** need to take care to combine the correct rows. First
653 ** alter the start and end time so that they are multiples
654 ** of the new step time. We cannot reduce the amount of
655 ** time so we have to move the end towards the future and
656 ** the start towards the past.
658 end_offset = (*end) % (*step);
659 start_offset = (*start) % (*step);
661 /* If there is a start offset (which cannot be more than
662 ** one destination row), skip the appropriate number of
663 ** source rows and one destination row. The appropriate
664 ** number is what we do know (start_offset/cur_step) of
665 ** the new interval (*step/cur_step aka reduce_factor).
668 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
669 printf("row_cnt before: %lu\n", row_cnt);
672 (*start) = (*start) - start_offset;
673 skiprows = reduce_factor - start_offset / cur_step;
674 srcptr += skiprows * *ds_cnt;
675 for (col = 0; col < (*ds_cnt); col++)
680 printf("row_cnt between: %lu\n", row_cnt);
683 /* At the end we have some rows that are not going to be
684 ** used, the amount is end_offset/cur_step
687 (*end) = (*end) - end_offset + (*step);
688 skiprows = end_offset / cur_step;
692 printf("row_cnt after: %lu\n", row_cnt);
695 /* Sanity check: row_cnt should be multiple of reduce_factor */
696 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
698 if (row_cnt % reduce_factor) {
699 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
700 row_cnt, reduce_factor);
701 printf("BUG in reduce_data()\n");
705 /* Now combine reduce_factor intervals at a time
706 ** into one interval for the destination.
709 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
710 for (col = 0; col < (*ds_cnt); col++) {
711 rrd_value_t newval = DNAN;
712 unsigned long validval = 0;
714 for (i = 0; i < reduce_factor; i++) {
715 if (isnan(srcptr[i * (*ds_cnt) + col])) {
720 newval = srcptr[i * (*ds_cnt) + col];
729 newval += srcptr[i * (*ds_cnt) + col];
732 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
735 /* an interval contains a failure if any subintervals contained a failure */
737 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
740 newval = srcptr[i * (*ds_cnt) + col];
766 srcptr += (*ds_cnt) * reduce_factor;
767 row_cnt -= reduce_factor;
769 /* If we had to alter the endtime, we didn't have enough
770 ** source rows to fill the last row. Fill it with NaN.
773 for (col = 0; col < (*ds_cnt); col++)
776 row_cnt = ((*end) - (*start)) / *step;
778 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
779 row_cnt, *start, *end, *step);
780 for (col = 0; col < row_cnt; col++) {
781 printf("time %10lu: ", *start + (col + 1) * (*step));
782 for (i = 0; i < *ds_cnt; i++)
783 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
790 /* get the data required for the graphs from the
799 /* pull the data from the rrd files ... */
800 for (i = 0; i < (int) im->gdes_c; i++) {
801 /* only GF_DEF elements fetch data */
802 if (im->gdes[i].gf != GF_DEF)
806 /* do we have it already ? */
807 for (ii = 0; ii < i; ii++) {
808 if (im->gdes[ii].gf != GF_DEF)
810 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
811 && (im->gdes[i].cf == im->gdes[ii].cf)
812 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
813 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
814 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
815 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
816 /* OK, the data is already there.
817 ** Just copy the header portion
819 im->gdes[i].start = im->gdes[ii].start;
820 im->gdes[i].end = im->gdes[ii].end;
821 im->gdes[i].step = im->gdes[ii].step;
822 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
823 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
824 im->gdes[i].data = im->gdes[ii].data;
825 im->gdes[i].data_first = 0;
832 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
834 if ((rrd_fetch_fn(im->gdes[i].rrd,
840 &im->gdes[i].ds_namv,
841 &im->gdes[i].data)) == -1) {
844 im->gdes[i].data_first = 1;
846 if (ft_step < im->gdes[i].step) {
847 reduce_data(im->gdes[i].cf_reduce,
852 &im->gdes[i].ds_cnt, &im->gdes[i].data);
854 im->gdes[i].step = ft_step;
858 /* lets see if the required data source is really there */
859 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
860 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
864 if (im->gdes[i].ds == -1) {
865 rrd_set_error("No DS called '%s' in '%s'",
866 im->gdes[i].ds_nam, im->gdes[i].rrd);
874 /* evaluate the expressions in the CDEF functions */
876 /*************************************************************
878 *************************************************************/
880 long find_var_wrapper(
884 return find_var((image_desc_t *) arg1, key);
887 /* find gdes containing var*/
894 for (ii = 0; ii < im->gdes_c - 1; ii++) {
895 if ((im->gdes[ii].gf == GF_DEF
896 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
897 && (strcmp(im->gdes[ii].vname, key) == 0)) {
904 /* find the largest common denominator for all the numbers
905 in the 0 terminated num array */
912 for (i = 0; num[i + 1] != 0; i++) {
914 rest = num[i] % num[i + 1];
920 /* return i==0?num[i]:num[i-1]; */
924 /* run the rpn calculator on all the VDEF and CDEF arguments */
931 long *steparray, rpi;
936 rpnstack_init(&rpnstack);
938 for (gdi = 0; gdi < im->gdes_c; gdi++) {
939 /* Look for GF_VDEF and GF_CDEF in the same loop,
940 * so CDEFs can use VDEFs and vice versa
942 switch (im->gdes[gdi].gf) {
946 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
948 /* remove current shift */
949 vdp->start -= vdp->shift;
950 vdp->end -= vdp->shift;
953 if (im->gdes[gdi].shidx >= 0)
954 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
957 vdp->shift = im->gdes[gdi].shval;
959 /* normalize shift to multiple of consolidated step */
960 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
963 vdp->start += vdp->shift;
964 vdp->end += vdp->shift;
968 /* A VDEF has no DS. This also signals other parts
969 * of rrdtool that this is a VDEF value, not a CDEF.
971 im->gdes[gdi].ds_cnt = 0;
972 if (vdef_calc(im, gdi)) {
973 rrd_set_error("Error processing VDEF '%s'",
974 im->gdes[gdi].vname);
975 rpnstack_free(&rpnstack);
980 im->gdes[gdi].ds_cnt = 1;
981 im->gdes[gdi].ds = 0;
982 im->gdes[gdi].data_first = 1;
983 im->gdes[gdi].start = 0;
984 im->gdes[gdi].end = 0;
989 /* Find the variables in the expression.
990 * - VDEF variables are substituted by their values
991 * and the opcode is changed into OP_NUMBER.
992 * - CDEF variables are analized for their step size,
993 * the lowest common denominator of all the step
994 * sizes of the data sources involved is calculated
995 * and the resulting number is the step size for the
996 * resulting data source.
998 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
999 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1000 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1001 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1003 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1006 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1007 im->gdes[gdi].vname, im->gdes[ptr].vname);
1008 printf("DEBUG: value from vdef is %f\n",
1009 im->gdes[ptr].vf.val);
1011 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1012 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1013 } else { /* normal variables and PREF(variables) */
1015 /* add one entry to the array that keeps track of the step sizes of the
1016 * data sources going into the CDEF. */
1018 rrd_realloc(steparray,
1020 1) * sizeof(*steparray))) == NULL) {
1021 rrd_set_error("realloc steparray");
1022 rpnstack_free(&rpnstack);
1026 steparray[stepcnt - 1] = im->gdes[ptr].step;
1028 /* adjust start and end of cdef (gdi) so
1029 * that it runs from the latest start point
1030 * to the earliest endpoint of any of the
1031 * rras involved (ptr)
1034 if (im->gdes[gdi].start < im->gdes[ptr].start)
1035 im->gdes[gdi].start = im->gdes[ptr].start;
1037 if (im->gdes[gdi].end == 0 ||
1038 im->gdes[gdi].end > im->gdes[ptr].end)
1039 im->gdes[gdi].end = im->gdes[ptr].end;
1041 /* store pointer to the first element of
1042 * the rra providing data for variable,
1043 * further save step size and data source
1046 im->gdes[gdi].rpnp[rpi].data =
1047 im->gdes[ptr].data + im->gdes[ptr].ds;
1048 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1049 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1051 /* backoff the *.data ptr; this is done so
1052 * rpncalc() function doesn't have to treat
1053 * the first case differently
1055 } /* if ds_cnt != 0 */
1056 } /* if OP_VARIABLE */
1057 } /* loop through all rpi */
1059 /* move the data pointers to the correct period */
1060 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1061 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1062 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1063 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1065 im->gdes[gdi].start - im->gdes[ptr].start;
1068 im->gdes[gdi].rpnp[rpi].data +=
1069 (diff / im->gdes[ptr].step) *
1070 im->gdes[ptr].ds_cnt;
1074 if (steparray == NULL) {
1075 rrd_set_error("rpn expressions without DEF"
1076 " or CDEF variables are not supported");
1077 rpnstack_free(&rpnstack);
1080 steparray[stepcnt] = 0;
1081 /* Now find the resulting step. All steps in all
1082 * used RRAs have to be visited
1084 im->gdes[gdi].step = lcd(steparray);
1086 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1087 im->gdes[gdi].start)
1088 / im->gdes[gdi].step)
1089 * sizeof(double))) == NULL) {
1090 rrd_set_error("malloc im->gdes[gdi].data");
1091 rpnstack_free(&rpnstack);
1095 /* Step through the new cdef results array and
1096 * calculate the values
1098 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1099 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1100 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1102 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1103 * in this case we are advancing by timesteps;
1104 * we use the fact that time_t is a synonym for long
1106 if (rpn_calc(rpnp, &rpnstack, (long) now,
1107 im->gdes[gdi].data, ++dataidx) == -1) {
1108 /* rpn_calc sets the error string */
1109 rpnstack_free(&rpnstack);
1112 } /* enumerate over time steps within a CDEF */
1117 } /* enumerate over CDEFs */
1118 rpnstack_free(&rpnstack);
1122 static int AlmostEqual2sComplement(
1128 int aInt = *(int *) &A;
1129 int bInt = *(int *) &B;
1132 /* Make sure maxUlps is non-negative and small enough that the
1133 default NAN won't compare as equal to anything. */
1135 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1137 /* Make aInt lexicographically ordered as a twos-complement int */
1140 aInt = 0x80000000l - aInt;
1142 /* Make bInt lexicographically ordered as a twos-complement int */
1145 bInt = 0x80000000l - bInt;
1147 intDiff = abs(aInt - bInt);
1149 if (intDiff <= maxUlps)
1155 /* massage data so, that we get one value for each x coordinate in the graph */
1160 double pixstep = (double) (im->end - im->start)
1161 / (double) im->xsize; /* how much time
1162 passes in one pixel */
1164 double minval = DNAN, maxval = DNAN;
1166 unsigned long gr_time;
1168 /* memory for the processed data */
1169 for (i = 0; i < im->gdes_c; i++) {
1170 if ((im->gdes[i].gf == GF_LINE) ||
1171 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1172 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1173 * sizeof(rrd_value_t))) == NULL) {
1174 rrd_set_error("malloc data_proc");
1180 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1183 gr_time = im->start + pixstep * i; /* time of the current step */
1186 for (ii = 0; ii < im->gdes_c; ii++) {
1189 switch (im->gdes[ii].gf) {
1193 if (!im->gdes[ii].stack)
1195 value = im->gdes[ii].yrule;
1196 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1197 /* The time of the data doesn't necessarily match
1198 ** the time of the graph. Beware.
1200 vidx = im->gdes[ii].vidx;
1201 if (im->gdes[vidx].gf == GF_VDEF) {
1202 value = im->gdes[vidx].vf.val;
1204 if (((long int) gr_time >=
1205 (long int) im->gdes[vidx].start)
1206 && ((long int) gr_time <=
1207 (long int) im->gdes[vidx].end)) {
1208 value = im->gdes[vidx].data[(unsigned long)
1214 im->gdes[vidx].step)
1215 * im->gdes[vidx].ds_cnt +
1222 if (!isnan(value)) {
1224 im->gdes[ii].p_data[i] = paintval;
1225 /* GF_TICK: the data values are not
1226 ** relevant for min and max
1228 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1229 if ((isnan(minval) || paintval < minval) &&
1230 !(im->logarithmic && paintval <= 0.0))
1232 if (isnan(maxval) || paintval > maxval)
1236 im->gdes[ii].p_data[i] = DNAN;
1241 ("STACK should already be turned into LINE or AREA here");
1250 /* if min or max have not been asigned a value this is because
1251 there was no data in the graph ... this is not good ...
1252 lets set these to dummy values then ... */
1254 if (im->logarithmic) {
1266 /* adjust min and max values */
1267 /* for logscale we add something on top */
1268 if (isnan(im->minval)
1269 || ((!im->rigid) && im->minval > minval)
1271 if (im->logarithmic)
1272 im->minval = minval * 0.5;
1274 im->minval = minval;
1276 if (isnan(im->maxval)
1277 || (!im->rigid && im->maxval < maxval)
1279 if (im->logarithmic)
1280 im->maxval = maxval * 2.0;
1282 im->maxval = maxval;
1285 /* make sure min is smaller than max */
1286 if (im->minval > im->maxval) {
1288 im->minval = 0.99 * im->maxval;
1290 im->minval = 1.01 * im->maxval;
1293 /* make sure min and max are not equal */
1294 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1300 /* make sure min and max are not both zero */
1301 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1310 /* identify the point where the first gridline, label ... gets placed */
1312 time_t find_first_time(
1313 time_t start, /* what is the initial time */
1314 enum tmt_en baseint, /* what is the basic interval */
1315 long basestep /* how many if these do we jump a time */
1320 localtime_r(&start, &tm);
1324 tm. tm_sec -= tm.tm_sec % basestep;
1329 tm. tm_min -= tm.tm_min % basestep;
1335 tm. tm_hour -= tm.tm_hour % basestep;
1339 /* we do NOT look at the basestep for this ... */
1346 /* we do NOT look at the basestep for this ... */
1350 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1352 if (tm.tm_wday == 0)
1353 tm. tm_mday -= 7; /* we want the *previous* monday */
1361 tm. tm_mon -= tm.tm_mon % basestep;
1372 tm.tm_year + 1900) %basestep;
1378 /* identify the point where the next gridline, label ... gets placed */
1379 time_t find_next_time(
1380 time_t current, /* what is the initial time */
1381 enum tmt_en baseint, /* what is the basic interval */
1382 long basestep /* how many if these do we jump a time */
1388 localtime_r(¤t, &tm);
1393 tm. tm_sec += basestep;
1397 tm. tm_min += basestep;
1401 tm. tm_hour += basestep;
1405 tm. tm_mday += basestep;
1409 tm. tm_mday += 7 * basestep;
1413 tm. tm_mon += basestep;
1417 tm. tm_year += basestep;
1419 madetime = mktime(&tm);
1420 } while (madetime == -1); /* this is necessary to skip impssible times
1421 like the daylight saving time skips */
1427 /* calculate values required for PRINT and GPRINT functions */
1433 long i, ii, validsteps;
1436 int graphelement = 0;
1439 double magfact = -1;
1444 /* wow initializing tmvdef is quite a task :-) */
1445 time_t now = time(NULL);
1447 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) {
1456 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1457 rrd_set_error("realloc prdata");
1461 /* PRINT and GPRINT can now print VDEF generated values.
1462 * There's no need to do any calculations on them as these
1463 * calculations were already made.
1465 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1466 printval = im->gdes[vidx].vf.val;
1467 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1468 } else { /* need to calculate max,min,avg etcetera */
1469 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1470 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1473 for (ii = im->gdes[vidx].ds;
1474 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1475 if (!finite(im->gdes[vidx].data[ii]))
1477 if (isnan(printval)) {
1478 printval = im->gdes[vidx].data[ii];
1483 switch (im->gdes[i].cf) {
1487 case CF_DEVSEASONAL:
1491 printval += im->gdes[vidx].data[ii];
1494 printval = min(printval, im->gdes[vidx].data[ii]);
1498 printval = max(printval, im->gdes[vidx].data[ii]);
1501 printval = im->gdes[vidx].data[ii];
1504 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1505 if (validsteps > 1) {
1506 printval = (printval / validsteps);
1509 } /* prepare printval */
1511 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1512 /* Magfact is set to -1 upon entry to print_calc. If it
1513 * is still less than 0, then we need to run auto_scale.
1514 * Otherwise, put the value into the correct units. If
1515 * the value is 0, then do not set the symbol or magnification
1516 * so next the calculation will be performed again. */
1517 if (magfact < 0.0) {
1518 auto_scale(im, &printval, &si_symb, &magfact);
1519 if (printval == 0.0)
1522 printval /= magfact;
1524 *(++percent_s) = 's';
1525 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1526 auto_scale(im, &printval, &si_symb, &magfact);
1529 if (im->gdes[i].gf == GF_PRINT) {
1530 (*prdata)[prlines - 2] =
1531 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1532 (*prdata)[prlines - 1] = NULL;
1533 if (im->gdes[i].strftm) {
1534 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1535 im->gdes[i].format, &tmvdef);
1537 if (bad_format(im->gdes[i].format)) {
1538 rrd_set_error("bad format for PRINT in '%s'",
1539 im->gdes[i].format);
1542 #ifdef HAVE_SNPRINTF
1543 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1544 im->gdes[i].format, printval, si_symb);
1546 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1553 if (im->gdes[i].strftm) {
1554 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1555 im->gdes[i].format, &tmvdef);
1557 if (bad_format(im->gdes[i].format)) {
1558 rrd_set_error("bad format for GPRINT in '%s'",
1559 im->gdes[i].format);
1562 #ifdef HAVE_SNPRINTF
1563 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1564 im->gdes[i].format, printval, si_symb);
1566 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1579 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1580 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1585 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1586 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1595 #ifdef WITH_PIECHART
1603 ("STACK should already be turned into LINE or AREA here");
1608 return graphelement;
1612 /* place legends with color spots */
1618 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1619 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1620 int fill = 0, fill_last;
1622 int leg_x = border, leg_y = im->yimg;
1623 int leg_y_prev = im->yimg;
1626 int i, ii, mark = 0;
1627 char prt_fctn; /*special printfunctions */
1628 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1631 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1632 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1633 rrd_set_error("malloc for legspace");
1637 if (im->extra_flags & FULL_SIZE_MODE)
1638 leg_y = leg_y_prev =
1639 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1641 for (i = 0; i < im->gdes_c; i++) {
1644 /* hide legends for rules which are not displayed */
1646 if (im->gdes[i].gf == GF_TEXTALIGN) {
1647 default_txtalign = im->gdes[i].txtalign;
1650 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1651 if (im->gdes[i].gf == GF_HRULE &&
1652 (im->gdes[i].yrule < im->minval
1653 || im->gdes[i].yrule > im->maxval))
1654 im->gdes[i].legend[0] = '\0';
1656 if (im->gdes[i].gf == GF_VRULE &&
1657 (im->gdes[i].xrule < im->start
1658 || im->gdes[i].xrule > im->end))
1659 im->gdes[i].legend[0] = '\0';
1662 leg_cc = strlen(im->gdes[i].legend);
1664 /* is there a controle code ant the end of the legend string ? */
1665 /* and it is not a tab \\t */
1666 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1667 && im->gdes[i].legend[leg_cc - 1] != 't') {
1668 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1670 im->gdes[i].legend[leg_cc] = '\0';
1674 /* only valid control codes */
1675 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1680 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1682 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1683 im->gdes[i].legend, prt_fctn);
1688 if (prt_fctn == 'n') {
1692 /* remove exess space from the end of the legend for \g */
1693 while (prt_fctn == 'g' &&
1694 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1696 im->gdes[i].legend[leg_cc] = '\0';
1701 /* no interleg space if string ends in \g */
1702 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1705 fill += legspace[i];
1707 fill += gfx_get_text_width(im, fill + border,
1708 im->text_prop[TEXT_PROP_LEGEND].
1710 im->text_prop[TEXT_PROP_LEGEND].
1712 im->gdes[i].legend);
1717 /* who said there was a special tag ... ? */
1718 if (prt_fctn == 'g') {
1722 if (prt_fctn == '\0') {
1723 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1724 /* just one legend item is left right or center */
1725 switch (default_txtalign) {
1740 /* is it time to place the legends ? */
1741 if (fill > im->ximg - 2 * border) {
1749 if (leg_c == 1 && prt_fctn == 'j') {
1755 if (prt_fctn != '\0') {
1757 if (leg_c >= 2 && prt_fctn == 'j') {
1758 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1762 if (prt_fctn == 'c')
1763 leg_x = (im->ximg - fill) / 2.0;
1764 if (prt_fctn == 'r')
1765 leg_x = im->ximg - fill - border;
1767 for (ii = mark; ii <= i; ii++) {
1768 if (im->gdes[ii].legend[0] == '\0')
1769 continue; /* skip empty legends */
1770 im->gdes[ii].leg_x = leg_x;
1771 im->gdes[ii].leg_y = leg_y;
1773 gfx_get_text_width(im, leg_x,
1774 im->text_prop[TEXT_PROP_LEGEND].
1776 im->text_prop[TEXT_PROP_LEGEND].
1778 im->gdes[ii].legend)
1783 if (im->extra_flags & FULL_SIZE_MODE) {
1784 /* only add y space if there was text on the line */
1785 if (leg_x > border || prt_fctn == 's')
1786 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1787 if (prt_fctn == 's')
1788 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1790 if (leg_x > border || prt_fctn == 's')
1791 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1792 if (prt_fctn == 's')
1793 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1801 if (im->extra_flags & FULL_SIZE_MODE) {
1802 if (leg_y != leg_y_prev) {
1803 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1805 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1808 im->yimg = leg_y_prev;
1809 /* if we did place some legends we have to add vertical space */
1810 if (leg_y != im->yimg)
1811 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1818 /* create a grid on the graph. it determines what to do
1819 from the values of xsize, start and end */
1821 /* the xaxis labels are determined from the number of seconds per pixel
1822 in the requested graph */
1826 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;
1839 /* does the scale of this graph make it impossible to put lines
1840 on it? If so, give up. */
1841 if (isnan(scaledrange)) {
1845 /* find grid spaceing */
1847 if (isnan(im->ygridstep)) {
1848 if (im->extra_flags & ALTYGRID) {
1849 /* find the value with max number of digits. Get number of digits */
1852 (max(fabs(im->maxval), fabs(im->minval)) *
1853 im->viewfactor / im->magfact));
1854 if (decimals <= 0) /* everything is small. make place for zero */
1857 im->ygrid_scale.gridstep =
1859 floor(log10(range * im->viewfactor / im->magfact))) /
1860 im->viewfactor * im->magfact;
1862 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1863 im->ygrid_scale.gridstep = 0.1;
1864 /* should have at least 5 lines but no more then 15 */
1865 if (range / im->ygrid_scale.gridstep < 5)
1866 im->ygrid_scale.gridstep /= 10;
1867 if (range / im->ygrid_scale.gridstep > 15)
1868 im->ygrid_scale.gridstep *= 10;
1869 if (range / im->ygrid_scale.gridstep > 5) {
1870 im->ygrid_scale.labfact = 1;
1871 if (range / im->ygrid_scale.gridstep > 8)
1872 im->ygrid_scale.labfact = 2;
1874 im->ygrid_scale.gridstep /= 5;
1875 im->ygrid_scale.labfact = 5;
1879 (im->ygrid_scale.gridstep *
1880 (double) im->ygrid_scale.labfact * im->viewfactor /
1882 if (fractionals < 0) { /* small amplitude. */
1883 int len = decimals - fractionals + 1;
1885 if (im->unitslength < len + 2)
1886 im->unitslength = len + 2;
1887 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1888 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1890 int len = decimals + 1;
1892 if (im->unitslength < len + 2)
1893 im->unitslength = len + 2;
1894 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1895 (im->symbol != ' ' ? " %c" : ""));
1898 for (i = 0; ylab[i].grid > 0; i++) {
1899 pixel = im->ysize / (scaledrange / ylab[i].grid);
1905 for (i = 0; i < 4; i++) {
1906 if (pixel * ylab[gridind].lfac[i] >=
1907 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1908 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1913 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1916 im->ygrid_scale.gridstep = im->ygridstep;
1917 im->ygrid_scale.labfact = im->ylabfact;
1922 int draw_horizontal_grid(
1927 char graph_label[100];
1929 double X0 = im->xorigin;
1930 double X1 = im->xorigin + im->xsize;
1932 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1933 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1937 im->ygrid_scale.gridstep / (double) im->magfact *
1938 (double) im->viewfactor;
1939 MaxY = scaledstep * (double) egrid;
1940 for (i = sgrid; i <= egrid; i++) {
1941 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1942 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1944 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1945 && 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, im->ygrid_scale.labfmt,
1955 scaledstep * (double) i);
1958 sprintf(graph_label, "%4.1f",
1959 scaledstep * (double) i);
1961 sprintf(graph_label, "%4.0f",
1962 scaledstep * (double) i);
1966 char sisym = (i == 0 ? ' ' : im->symbol);
1968 if (im->extra_flags & ALTYGRID) {
1969 sprintf(graph_label, im->ygrid_scale.labfmt,
1970 scaledstep * (double) i, sisym);
1973 sprintf(graph_label, "%4.1f %c",
1974 scaledstep * (double) i, sisym);
1976 sprintf(graph_label, "%4.0f %c",
1977 scaledstep * (double) i, sisym);
1984 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1985 im->graph_col[GRC_FONT],
1986 im->text_prop[TEXT_PROP_AXIS].font,
1987 im->text_prop[TEXT_PROP_AXIS].size,
1988 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1992 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1995 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1999 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2000 im->grid_dash_on, im->grid_dash_off);
2002 } else if (!(im->extra_flags & NOMINOR)) {
2005 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2008 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2012 GRIDWIDTH, im->graph_col[GRC_GRID],
2013 im->grid_dash_on, im->grid_dash_off);
2021 /* this is frexp for base 10 */
2032 iexp = floor(log(fabs(x)) / log(10));
2033 mnt = x / pow(10.0, iexp);
2036 mnt = x / pow(10.0, iexp);
2042 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2043 /* yes we are loosing precision by doing tos with floats instead of doubles
2044 but it seems more stable this way. */
2047 /* logaritmic horizontal grid */
2048 int horizontal_log_grid(
2051 double yloglab[][10] = {
2052 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2053 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2054 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2055 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2056 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2057 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2060 int i, j, val_exp, min_exp;
2061 double nex; /* number of decades in data */
2062 double logscale; /* scale in logarithmic space */
2063 int exfrac = 1; /* decade spacing */
2064 int mid = -1; /* row in yloglab for major grid */
2065 double mspac; /* smallest major grid spacing (pixels) */
2066 int flab; /* first value in yloglab to use */
2067 double value, tmp, pre_value;
2069 char graph_label[100];
2071 nex = log10(im->maxval / im->minval);
2072 logscale = im->ysize / nex;
2074 /* major spacing for data with high dynamic range */
2075 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2082 /* major spacing for less dynamic data */
2084 /* search best row in yloglab */
2086 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2087 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2088 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2089 && yloglab[mid][0] > 0);
2093 /* find first value in yloglab */
2095 yloglab[mid][flab] < 10
2096 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2097 if (yloglab[mid][flab] == 10.0) {
2102 if (val_exp % exfrac)
2103 val_exp += abs(-val_exp % exfrac);
2106 X1 = im->xorigin + im->xsize;
2112 value = yloglab[mid][flab] * pow(10.0, val_exp);
2113 if (AlmostEqual2sComplement(value, pre_value, 4))
2114 break; /* it seems we are not converging */
2118 Y0 = ytr(im, value);
2119 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2122 /* major grid line */
2125 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2127 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2133 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2134 im->grid_dash_on, im->grid_dash_off);
2137 if (im->extra_flags & FORCE_UNITS_SI) {
2142 scale = floor(val_exp / 3.0);
2144 pvalue = pow(10.0, val_exp % 3);
2146 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2147 pvalue *= yloglab[mid][flab];
2149 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2150 ((scale + si_symbcenter) >= 0))
2151 symbol = si_symbol[scale + si_symbcenter];
2155 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2157 sprintf(graph_label, "%3.0e", value);
2159 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2160 im->graph_col[GRC_FONT],
2161 im->text_prop[TEXT_PROP_AXIS].font,
2162 im->text_prop[TEXT_PROP_AXIS].size,
2163 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2166 if (mid < 4 && exfrac == 1) {
2167 /* find first and last minor line behind current major line
2168 * i is the first line and j tha last */
2170 min_exp = val_exp - 1;
2171 for (i = 1; yloglab[mid][i] < 10.0; i++);
2172 i = yloglab[mid][i - 1] + 1;
2176 i = yloglab[mid][flab - 1] + 1;
2177 j = yloglab[mid][flab];
2180 /* draw minor lines below current major line */
2181 for (; i < j; i++) {
2183 value = i * pow(10.0, min_exp);
2184 if (value < im->minval)
2187 Y0 = ytr(im, value);
2188 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2194 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2197 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2201 GRIDWIDTH, im->graph_col[GRC_GRID],
2202 im->grid_dash_on, im->grid_dash_off);
2204 } else if (exfrac > 1) {
2205 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2206 value = pow(10.0, i);
2207 if (value < im->minval)
2210 Y0 = ytr(im, value);
2211 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2217 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2220 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2224 GRIDWIDTH, im->graph_col[GRC_GRID],
2225 im->grid_dash_on, im->grid_dash_off);
2230 if (yloglab[mid][++flab] == 10.0) {
2236 /* draw minor lines after highest major line */
2237 if (mid < 4 && exfrac == 1) {
2238 /* find first and last minor line below current major line
2239 * i is the first line and j tha last */
2241 min_exp = val_exp - 1;
2242 for (i = 1; yloglab[mid][i] < 10.0; i++);
2243 i = yloglab[mid][i - 1] + 1;
2247 i = yloglab[mid][flab - 1] + 1;
2248 j = yloglab[mid][flab];
2251 /* draw minor lines below current major line */
2252 for (; i < j; i++) {
2254 value = i * pow(10.0, min_exp);
2255 if (value < im->minval)
2258 Y0 = ytr(im, value);
2259 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2264 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2266 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2270 GRIDWIDTH, im->graph_col[GRC_GRID],
2271 im->grid_dash_on, im->grid_dash_off);
2274 /* fancy minor gridlines */
2275 else if (exfrac > 1) {
2276 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2277 value = pow(10.0, i);
2278 if (value < im->minval)
2281 Y0 = ytr(im, value);
2282 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2287 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2289 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2293 GRIDWIDTH, im->graph_col[GRC_GRID],
2294 im->grid_dash_on, im->grid_dash_off);
2305 int xlab_sel; /* which sort of label and grid ? */
2306 time_t ti, tilab, timajor;
2308 char graph_label[100];
2309 double X0, Y0, Y1; /* points for filled graph and more */
2312 /* the type of time grid is determined by finding
2313 the number of seconds per pixel in the graph */
2316 if (im->xlab_user.minsec == -1) {
2317 factor = (im->end - im->start) / im->xsize;
2319 while (xlab[xlab_sel + 1].minsec != -1
2320 && xlab[xlab_sel + 1].minsec <= factor) {
2322 } /* pick the last one */
2323 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2324 && xlab[xlab_sel].length > (im->end - im->start)) {
2326 } /* go back to the smallest size */
2327 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2328 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2329 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2330 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2331 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2332 im->xlab_user.labst = xlab[xlab_sel].labst;
2333 im->xlab_user.precis = xlab[xlab_sel].precis;
2334 im->xlab_user.stst = xlab[xlab_sel].stst;
2337 /* y coords are the same for every line ... */
2339 Y1 = im->yorigin - im->ysize;
2342 /* paint the minor grid */
2343 if (!(im->extra_flags & NOMINOR)) {
2344 for (ti = find_first_time(im->start,
2345 im->xlab_user.gridtm,
2346 im->xlab_user.gridst),
2347 timajor = find_first_time(im->start,
2348 im->xlab_user.mgridtm,
2349 im->xlab_user.mgridst);
2352 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2354 /* are we inside the graph ? */
2355 if (ti < im->start || ti > im->end)
2357 while (timajor < ti) {
2358 timajor = find_next_time(timajor,
2359 im->xlab_user.mgridtm,
2360 im->xlab_user.mgridst);
2363 continue; /* skip as falls on major grid line */
2365 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2366 im->graph_col[GRC_GRID]);
2367 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2368 im->graph_col[GRC_GRID]);
2369 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2370 im->graph_col[GRC_GRID],
2371 im->grid_dash_on, im->grid_dash_off);
2376 /* paint the major grid */
2377 for (ti = find_first_time(im->start,
2378 im->xlab_user.mgridtm,
2379 im->xlab_user.mgridst);
2381 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2383 /* are we inside the graph ? */
2384 if (ti < im->start || ti > im->end)
2387 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2388 im->graph_col[GRC_MGRID]);
2389 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2390 im->graph_col[GRC_MGRID]);
2391 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2392 im->graph_col[GRC_MGRID],
2393 im->grid_dash_on, im->grid_dash_off);
2396 /* paint the labels below the graph */
2397 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2398 im->xlab_user.labtm,
2399 im->xlab_user.labst);
2400 ti <= im->end - im->xlab_user.precis / 2;
2401 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2403 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2404 /* are we inside the graph ? */
2405 if (tilab < im->start || tilab > im->end)
2409 localtime_r(&tilab, &tm);
2410 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2412 # error "your libc has no strftime I guess we'll abort the exercise here."
2417 im->graph_col[GRC_FONT],
2418 im->text_prop[TEXT_PROP_AXIS].font,
2419 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2420 GFX_H_CENTER, GFX_V_TOP, graph_label);
2430 /* draw x and y axis */
2431 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2432 im->xorigin+im->xsize,im->yorigin-im->ysize,
2433 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2435 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2436 im->xorigin+im->xsize,im->yorigin-im->ysize,
2437 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2439 gfx_line(im, im->xorigin - 4, im->yorigin,
2440 im->xorigin + im->xsize + 4, im->yorigin,
2441 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2443 gfx_line(im, im->xorigin, im->yorigin + 4,
2444 im->xorigin, im->yorigin - im->ysize - 4,
2445 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2448 /* arrow for X and Y axis direction */
2450 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 */
2451 im->graph_col[GRC_ARROW]);
2454 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 */
2455 im->graph_col[GRC_ARROW]);
2466 double X0, Y0; /* points for filled graph and more */
2467 struct gfx_color_t water_color;
2469 /* draw 3d border */
2470 gfx_new_area(im, 0, im->yimg,
2471 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2472 gfx_add_point(im, im->ximg - 2, 2);
2473 gfx_add_point(im, im->ximg, 0);
2474 gfx_add_point(im, 0, 0);
2477 gfx_new_area(im, 2, im->yimg - 2,
2478 im->ximg - 2, im->yimg - 2,
2479 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2480 gfx_add_point(im, im->ximg, 0);
2481 gfx_add_point(im, im->ximg, im->yimg);
2482 gfx_add_point(im, 0, im->yimg);
2486 if (im->draw_x_grid == 1)
2489 if (im->draw_y_grid == 1) {
2490 if (im->logarithmic) {
2491 res = horizontal_log_grid(im);
2493 res = draw_horizontal_grid(im);
2496 /* dont draw horizontal grid if there is no min and max val */
2498 char *nodata = "No Data found";
2500 gfx_text(im, im->ximg / 2,
2501 (2 * im->yorigin - im->ysize) / 2,
2502 im->graph_col[GRC_FONT],
2503 im->text_prop[TEXT_PROP_AXIS].font,
2504 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2505 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2509 /* yaxis unit description */
2511 10, (im->yorigin - im->ysize / 2),
2512 im->graph_col[GRC_FONT],
2513 im->text_prop[TEXT_PROP_UNIT].font,
2514 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2515 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2520 im->graph_col[GRC_FONT],
2521 im->text_prop[TEXT_PROP_TITLE].font,
2522 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2523 GFX_H_CENTER, GFX_V_TOP, im->title);
2524 /* rrdtool 'logo' */
2525 water_color = im->graph_col[GRC_FONT];
2526 water_color.alpha = 0.3;
2530 im->text_prop[TEXT_PROP_AXIS].font,
2531 5.5, im->tabwidth, -90,
2532 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2534 /* graph watermark */
2535 if (im->watermark[0] != '\0') {
2537 im->ximg / 2, im->yimg - 6,
2539 im->text_prop[TEXT_PROP_AXIS].font,
2540 5.5, im->tabwidth, 0,
2541 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2545 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2546 for (i = 0; i < im->gdes_c; i++) {
2547 if (im->gdes[i].legend[0] == '\0')
2550 /* im->gdes[i].leg_y is the bottom of the legend */
2551 X0 = im->gdes[i].leg_x;
2552 Y0 = im->gdes[i].leg_y;
2553 gfx_text(im, X0, Y0,
2554 im->graph_col[GRC_FONT],
2555 im->text_prop[TEXT_PROP_LEGEND].font,
2556 im->text_prop[TEXT_PROP_LEGEND].size,
2557 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2558 im->gdes[i].legend);
2559 /* The legend for GRAPH items starts with "M " to have
2560 enough space for the box */
2561 if (im->gdes[i].gf != GF_PRINT &&
2562 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2567 boxH = gfx_get_text_width(im, 0,
2568 im->text_prop[TEXT_PROP_LEGEND].
2570 im->text_prop[TEXT_PROP_LEGEND].
2571 size, im->tabwidth, "o") * 1.2;
2574 /* shift the box up a bit */
2577 /* make sure transparent colors show up the same way as in the graph */
2581 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2582 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2587 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2588 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2592 cairo_new_path(im->cr);
2593 cairo_set_line_width(im->cr, 1.0);
2596 gfx_line_fit(im, &X0, &Y0);
2597 gfx_line_fit(im, &X1, &Y1);
2598 cairo_move_to(im->cr, X0, Y0);
2599 cairo_line_to(im->cr, X1, Y0);
2600 cairo_line_to(im->cr, X1, Y1);
2601 cairo_line_to(im->cr, X0, Y1);
2602 cairo_close_path(im->cr);
2603 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2604 im->graph_col[GRC_FRAME].green,
2605 im->graph_col[GRC_FRAME].blue,
2606 im->graph_col[GRC_FRAME].alpha);
2607 if (im->gdes[i].dash) {
2608 // make box borders in legend dashed if the graph is dashed
2609 double dashes[] = { 3.0 };
2610 cairo_set_dash(im->cr, dashes, 1, 0.0);
2612 cairo_stroke(im->cr);
2613 cairo_restore(im->cr);
2620 /*****************************************************
2621 * lazy check make sure we rely need to create this graph
2622 *****************************************************/
2629 struct stat imgstat;
2632 return 0; /* no lazy option */
2633 if (stat(im->graphfile, &imgstat) != 0)
2634 return 0; /* can't stat */
2635 /* one pixel in the existing graph is more then what we would
2637 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2639 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2640 return 0; /* the file does not exist */
2641 switch (im->imgformat) {
2643 size = PngSize(fd, &(im->ximg), &(im->yimg));
2653 int graph_size_location(
2657 /* The actual size of the image to draw is determined from
2658 ** several sources. The size given on the command line is
2659 ** the graph area but we need more as we have to draw labels
2660 ** and other things outside the graph area
2663 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2664 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2666 if (im->extra_flags & ONLY_GRAPH) {
2668 im->ximg = im->xsize;
2669 im->yimg = im->ysize;
2670 im->yorigin = im->ysize;
2675 /** +---+--------------------------------------------+
2676 ** | y |...............graph title..................|
2677 ** | +---+-------------------------------+--------+
2680 ** | i | a | | pie |
2681 ** | s | x | main graph area | chart |
2686 ** | l | b +-------------------------------+--------+
2687 ** | e | l | x axis labels | |
2688 ** +---+---+-------------------------------+--------+
2689 ** |....................legends.....................|
2690 ** +------------------------------------------------+
2692 ** +------------------------------------------------+
2695 if (im->ylegend[0] != '\0') {
2696 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2699 if (im->title[0] != '\0') {
2700 /* The title is placed "inbetween" two text lines so it
2701 ** automatically has some vertical spacing. The horizontal
2702 ** spacing is added here, on each side.
2704 /* if necessary, reduce the font size of the title until it fits the image width */
2705 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2709 if (im->draw_x_grid) {
2710 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2712 if (im->draw_y_grid || im->forceleftspace) {
2713 Xylabel = gfx_get_text_width(im, 0,
2714 im->text_prop[TEXT_PROP_AXIS].font,
2715 im->text_prop[TEXT_PROP_AXIS].size,
2716 im->tabwidth, "0") * im->unitslength;
2720 if (im->extra_flags & FULL_SIZE_MODE) {
2721 /* The actual size of the image to draw has been determined by the user.
2722 ** The graph area is the space remaining after accounting for the legend,
2723 ** the watermark, the pie chart, the axis labels, and the title.
2726 im->ximg = im->xsize;
2727 im->yimg = im->ysize;
2728 im->yorigin = im->ysize;
2732 im->yorigin += Ytitle;
2734 /* Now calculate the total size. Insert some spacing where
2735 desired. im->xorigin and im->yorigin need to correspond
2736 with the lower left corner of the main graph area or, if
2737 this one is not set, the imaginary box surrounding the
2740 /* Initial size calculation for the main graph area */
2741 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2743 Xmain -= Xspacing; /* put space between main graph area and right edge */
2745 im->xorigin = Xspacing + Xylabel;
2747 /* the length of the title should not influence with width of the graph
2748 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2750 if (Xvertical) { /* unit description */
2752 im->xorigin += Xvertical;
2757 /* The vertical size of the image is known in advance. The main graph area
2758 ** (Ymain) and im->yorigin must be set according to the space requirements
2759 ** of the legend and the axis labels.
2762 if (im->extra_flags & NOLEGEND) {
2763 /* set dimensions correctly if using full size mode with no legend */
2765 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2767 Ymain = im->yorigin;
2769 /* Determine where to place the legends onto the image.
2770 ** Set Ymain and adjust im->yorigin to match the space requirements.
2772 if (leg_place(im, &Ymain) == -1)
2777 /* remove title space *or* some padding above the graph from the main graph area */
2781 Ymain -= 1.5 * Yspacing;
2784 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2785 if (im->watermark[0] != '\0') {
2786 Ymain -= Ywatermark;
2791 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2793 /* The actual size of the image to draw is determined from
2794 ** several sources. The size given on the command line is
2795 ** the graph area but we need more as we have to draw labels
2796 ** and other things outside the graph area.
2799 if (im->ylegend[0] != '\0') {
2800 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2804 if (im->title[0] != '\0') {
2805 /* The title is placed "inbetween" two text lines so it
2806 ** automatically has some vertical spacing. The horizontal
2807 ** spacing is added here, on each side.
2809 /* don't care for the with of the title
2810 Xtitle = gfx_get_text_width(im->canvas, 0,
2811 im->text_prop[TEXT_PROP_TITLE].font,
2812 im->text_prop[TEXT_PROP_TITLE].size,
2814 im->title, 0) + 2*Xspacing; */
2815 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2822 /* Now calculate the total size. Insert some spacing where
2823 desired. im->xorigin and im->yorigin need to correspond
2824 with the lower left corner of the main graph area or, if
2825 this one is not set, the imaginary box surrounding the
2828 /* The legend width cannot yet be determined, as a result we
2829 ** have problems adjusting the image to it. For now, we just
2830 ** forget about it at all; the legend will have to fit in the
2831 ** size already allocated.
2833 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2836 im->ximg += Xspacing;
2838 im->xorigin = Xspacing + Xylabel;
2840 /* the length of the title should not influence with width of the graph
2841 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2843 if (Xvertical) { /* unit description */
2844 im->ximg += Xvertical;
2845 im->xorigin += Xvertical;
2849 /* The vertical size is interesting... we need to compare
2850 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2851 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2852 ** in order to start even thinking about Ylegend or Ywatermark.
2854 ** Do it in three portions: First calculate the inner part,
2855 ** then do the legend, then adjust the total height of the img,
2856 ** adding space for a watermark if one exists;
2859 /* reserve space for main and/or pie */
2861 im->yimg = Ymain + Yxlabel;
2864 im->yorigin = im->yimg - Yxlabel;
2866 /* reserve space for the title *or* some padding above the graph */
2869 im->yorigin += Ytitle;
2871 im->yimg += 1.5 * Yspacing;
2872 im->yorigin += 1.5 * Yspacing;
2874 /* reserve space for padding below the graph */
2875 im->yimg += Yspacing;
2877 /* Determine where to place the legends onto the image.
2878 ** Adjust im->yimg to match the space requirements.
2880 if (leg_place(im, 0) == -1)
2883 if (im->watermark[0] != '\0') {
2884 im->yimg += Ywatermark;
2894 static cairo_status_t cairo_write_func_filehandle(
2896 const unsigned char *data,
2897 unsigned int length)
2899 if (fwrite(data, length, 1, closure) != 1)
2900 return CAIRO_STATUS_WRITE_ERROR;
2901 return CAIRO_STATUS_SUCCESS;
2904 static cairo_status_t cairo_copy_to_buffer(
2906 const unsigned char *data,
2907 unsigned int length)
2909 image_desc_t *im = closure;
2911 im->rendered_image =
2912 realloc(im->rendered_image, im->rendered_image_size + length);
2913 if (im->rendered_image == NULL) {
2914 return CAIRO_STATUS_WRITE_ERROR;
2917 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2919 im->rendered_image_size += length;
2921 return CAIRO_STATUS_SUCCESS;
2924 /* draw that picture thing ... */
2930 int lazy = lazy_check(im);
2932 double areazero = 0.0;
2933 graph_desc_t *lastgdes = NULL;
2935 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2938 /* if we are lazy and there is nothing to PRINT ... quit now */
2939 if (lazy && im->prt_c == 0)
2942 /* pull the data from the rrd files ... */
2944 if (data_fetch(im) == -1)
2947 /* evaluate VDEF and CDEF operations ... */
2948 if (data_calc(im) == -1)
2952 /* calculate and PRINT and GPRINT definitions. We have to do it at
2953 * this point because it will affect the length of the legends
2954 * if there are no graph elements we stop here ...
2955 * if we are lazy, try to quit ...
2957 i = print_calc(im, calcpr);
2960 if ((i == 0) || lazy)
2963 /**************************************************************
2964 *** Calculating sizes and locations became a bit confusing ***
2965 *** so I moved this into a separate function. ***
2966 **************************************************************/
2967 if (graph_size_location(im, i) == -1)
2970 /* get actual drawing data and find min and max values */
2971 if (data_proc(im) == -1)
2974 if (!im->logarithmic) {
2977 /* identify si magnitude Kilo, Mega Giga ? */
2978 if (!im->rigid && !im->logarithmic)
2979 expand_range(im); /* make sure the upper and lower limit are
2982 if (!calc_horizontal_grid(im))
2989 apply_gridfit(im); */
2992 /* the actual graph is created by going through the individual
2993 graph elements and then drawing them */
2994 cairo_surface_destroy(im->surface);
2996 switch (im->imgformat) {
2999 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3000 im->ximg * im->zoom,
3001 im->yimg * im->zoom);
3005 im->surface = strlen(im->graphfile)
3006 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3007 im->yimg * im->zoom)
3008 : cairo_pdf_surface_create_for_stream(&cairo_copy_to_buffer, im,
3009 im->ximg * im->zoom,
3010 im->yimg * im->zoom);
3014 im->surface = strlen(im->graphfile)
3015 ? cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3016 im->yimg * im->zoom)
3017 : cairo_ps_surface_create_for_stream(&cairo_copy_to_buffer, im,
3018 im->ximg * im->zoom,
3019 im->yimg * im->zoom);
3023 im->surface = strlen(im->graphfile)
3024 ? cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
3025 im->yimg * im->zoom)
3026 : cairo_svg_surface_create_for_stream(&cairo_copy_to_buffer, im,
3027 im->ximg * im->zoom,
3028 im->yimg * im->zoom);
3029 cairo_svg_surface_restrict_to_version(im->surface,
3030 CAIRO_SVG_VERSION_1_1);
3033 im->cr = cairo_create(im->surface);
3034 cairo_set_antialias(im->cr, im->graph_antialias);
3035 cairo_scale(im->cr, im->zoom, im->zoom);
3036 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3040 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3042 gfx_add_point(im, im->ximg, 0);
3046 im->xorigin, im->yorigin,
3047 im->xorigin + im->xsize, im->yorigin,
3048 im->xorigin + im->xsize, im->yorigin - im->ysize,
3049 im->graph_col[GRC_CANVAS]);
3051 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3054 if (im->minval > 0.0)
3055 areazero = im->minval;
3056 if (im->maxval < 0.0)
3057 areazero = im->maxval;
3059 for (i = 0; i < im->gdes_c; i++) {
3060 switch (im->gdes[i].gf) {
3074 for (ii = 0; ii < im->xsize; ii++) {
3075 if (!isnan(im->gdes[i].p_data[ii]) &&
3076 im->gdes[i].p_data[ii] != 0.0) {
3077 if (im->gdes[i].yrule > 0) {
3079 im->xorigin + ii, im->yorigin,
3082 im->gdes[i].yrule * im->ysize, 1.0,
3084 } else if (im->gdes[i].yrule < 0) {
3087 im->yorigin - im->ysize,
3090 im->gdes[i].yrule) *
3091 im->ysize, 1.0, im->gdes[i].col);
3099 /* fix data points at oo and -oo */
3100 for (ii = 0; ii < im->xsize; ii++) {
3101 if (isinf(im->gdes[i].p_data[ii])) {
3102 if (im->gdes[i].p_data[ii] > 0) {
3103 im->gdes[i].p_data[ii] = im->maxval;
3105 im->gdes[i].p_data[ii] = im->minval;
3111 /* *******************************************************
3116 -------|--t-1--t--------------------------------
3118 if we know the value at time t was a then
3119 we draw a square from t-1 to t with the value a.
3121 ********************************************************* */
3122 if (im->gdes[i].col.alpha != 0.0) {
3123 /* GF_LINE and friend */
3124 if (im->gdes[i].gf == GF_LINE) {
3125 double last_y = 0.0;
3129 cairo_new_path(im->cr);
3131 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3133 if (im->gdes[i].dash) {
3134 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3135 im->gdes[i].ndash, im->gdes[i].offset);
3138 for (ii = 1; ii < im->xsize; ii++) {
3139 if (isnan(im->gdes[i].p_data[ii])
3140 || (im->slopemode == 1
3141 && isnan(im->gdes[i].p_data[ii - 1]))) {
3146 last_y = ytr(im, im->gdes[i].p_data[ii]);
3147 if (im->slopemode == 0) {
3148 double x = ii - 1 + im->xorigin;
3151 gfx_line_fit(im, &x, &y);
3152 cairo_move_to(im->cr, x, y);
3153 x = ii + im->xorigin;
3155 gfx_line_fit(im, &x, &y);
3156 cairo_line_to(im->cr, x, y);
3158 double x = ii - 1 + im->xorigin;
3160 im->gdes[i].p_data[ii - 1]);
3162 gfx_line_fit(im, &x, &y);
3163 cairo_move_to(im->cr, x, y);
3164 x = ii + im->xorigin;
3166 gfx_line_fit(im, &x, &y);
3167 cairo_line_to(im->cr, x, y);
3171 double x1 = ii + im->xorigin;
3172 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3174 if (im->slopemode == 0
3175 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3176 double x = ii - 1 + im->xorigin;
3179 gfx_line_fit(im, &x, &y);
3180 cairo_line_to(im->cr, x, y);
3183 gfx_line_fit(im, &x1, &y1);
3184 cairo_line_to(im->cr, x1, y1);
3188 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3189 im->gdes[i].col.green,
3190 im->gdes[i].col.blue,
3191 im->gdes[i].col.alpha);
3192 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3193 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3194 cairo_stroke(im->cr);
3195 cairo_restore(im->cr);
3199 (double *) malloc(sizeof(double) * im->xsize * 2);
3201 (double *) malloc(sizeof(double) * im->xsize * 2);
3203 (double *) malloc(sizeof(double) * im->xsize * 2);
3205 (double *) malloc(sizeof(double) * im->xsize * 2);
3208 for (ii = 0; ii <= im->xsize; ii++) {
3211 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3216 && AlmostEqual2sComplement(foreY[lastI],
3218 && AlmostEqual2sComplement(foreY[lastI],
3226 foreX[cntI], foreY[cntI],
3228 while (cntI < idxI) {
3233 AlmostEqual2sComplement(foreY[lastI],
3236 AlmostEqual2sComplement(foreY[lastI],
3241 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3243 gfx_add_point(im, backX[idxI], backY[idxI]);
3249 AlmostEqual2sComplement(backY[lastI],
3252 AlmostEqual2sComplement(backY[lastI],
3257 gfx_add_point(im, backX[idxI], backY[idxI]);
3267 if (ii == im->xsize)
3270 if (im->slopemode == 0 && ii == 0) {
3273 if (isnan(im->gdes[i].p_data[ii])) {
3277 ytop = ytr(im, im->gdes[i].p_data[ii]);
3278 if (lastgdes && im->gdes[i].stack) {
3279 ybase = ytr(im, lastgdes->p_data[ii]);
3281 ybase = ytr(im, areazero);
3283 if (ybase == ytop) {
3289 double extra = ytop;
3294 if (im->slopemode == 0) {
3295 backY[++idxI] = ybase - 0.2;
3296 backX[idxI] = ii + im->xorigin - 1;
3297 foreY[idxI] = ytop + 0.2;
3298 foreX[idxI] = ii + im->xorigin - 1;
3300 backY[++idxI] = ybase - 0.2;
3301 backX[idxI] = ii + im->xorigin;
3302 foreY[idxI] = ytop + 0.2;
3303 foreX[idxI] = ii + im->xorigin;
3305 /* close up any remaining area */
3310 } /* else GF_LINE */
3312 /* if color != 0x0 */
3313 /* make sure we do not run into trouble when stacking on NaN */
3314 for (ii = 0; ii < im->xsize; ii++) {
3315 if (isnan(im->gdes[i].p_data[ii])) {
3316 if (lastgdes && (im->gdes[i].stack)) {
3317 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3319 im->gdes[i].p_data[ii] = areazero;
3323 lastgdes = &(im->gdes[i]);
3327 ("STACK should already be turned into LINE or AREA here");
3334 /* grid_paint also does the text */
3335 if (!(im->extra_flags & ONLY_GRAPH))
3339 if (!(im->extra_flags & ONLY_GRAPH))
3342 /* the RULES are the last thing to paint ... */
3343 for (i = 0; i < im->gdes_c; i++) {
3345 switch (im->gdes[i].gf) {
3347 if (im->gdes[i].yrule >= im->minval
3348 && im->gdes[i].yrule <= im->maxval)
3350 if (im->gdes[i].dash) {
3351 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3352 im->gdes[i].ndash, im->gdes[i].offset);
3355 im->xorigin, ytr(im, im->gdes[i].yrule),
3356 im->xorigin + im->xsize, ytr(im,
3358 1.0, im->gdes[i].col);
3359 cairo_stroke(im->cr);
3360 cairo_restore(im->cr);
3363 if (im->gdes[i].xrule >= im->start
3364 && im->gdes[i].xrule <= im->end)
3366 if (im->gdes[i].dash) {
3367 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3368 im->gdes[i].ndash, im->gdes[i].offset);
3371 xtr(im, im->gdes[i].xrule), im->yorigin,
3372 xtr(im, im->gdes[i].xrule),
3373 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3374 cairo_stroke(im->cr);
3375 cairo_restore(im->cr);
3383 switch (im->imgformat) {
3386 cairo_status_t status;
3388 if (strlen(im->graphfile) == 0) {
3390 cairo_surface_write_to_png_stream(im->surface,
3391 &cairo_copy_to_buffer, im);
3392 } else if (strcmp(im->graphfile, "-") == 0) {
3394 cairo_surface_write_to_png_stream(im->surface,
3395 &cairo_write_func_filehandle,
3398 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3401 if (status != CAIRO_STATUS_SUCCESS) {
3402 rrd_set_error("Could not save png to '%s'", im->graphfile);
3408 if (strlen(im->graphfile)) {
3409 cairo_show_page(im->cr);
3411 cairo_surface_finish(im->surface);
3419 /*****************************************************
3421 *****************************************************/
3428 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3429 * sizeof(graph_desc_t))) ==
3431 rrd_set_error("realloc graph_descs");
3436 im->gdes[im->gdes_c - 1].step = im->step;
3437 im->gdes[im->gdes_c - 1].step_orig = im->step;
3438 im->gdes[im->gdes_c - 1].stack = 0;
3439 im->gdes[im->gdes_c - 1].linewidth = 0;
3440 im->gdes[im->gdes_c - 1].debug = 0;
3441 im->gdes[im->gdes_c - 1].start = im->start;
3442 im->gdes[im->gdes_c - 1].start_orig = im->start;
3443 im->gdes[im->gdes_c - 1].end = im->end;
3444 im->gdes[im->gdes_c - 1].end_orig = im->end;
3445 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3446 im->gdes[im->gdes_c - 1].data = NULL;
3447 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3448 im->gdes[im->gdes_c - 1].data_first = 0;
3449 im->gdes[im->gdes_c - 1].p_data = NULL;
3450 im->gdes[im->gdes_c - 1].rpnp = NULL;
3451 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3452 im->gdes[im->gdes_c - 1].shift = 0.0;
3453 im->gdes[im->gdes_c - 1].col.red = 0.0;
3454 im->gdes[im->gdes_c - 1].col.green = 0.0;
3455 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3456 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3457 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3458 im->gdes[im->gdes_c - 1].format[0] = '\0';
3459 im->gdes[im->gdes_c - 1].strftm = 0;
3460 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3461 im->gdes[im->gdes_c - 1].ds = -1;
3462 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3463 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3464 im->gdes[im->gdes_c - 1].yrule = DNAN;
3465 im->gdes[im->gdes_c - 1].xrule = 0;
3469 /* copies input untill the first unescaped colon is found
3470 or until input ends. backslashes have to be escaped as well */
3472 const char *const input,
3478 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3479 if (input[inp] == '\\' &&
3480 input[inp + 1] != '\0' &&
3481 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3482 output[outp++] = input[++inp];
3484 output[outp++] = input[inp];
3487 output[outp] = '\0';
3491 /* Some surgery done on this function, it became ridiculously big.
3493 ** - initializing now in rrd_graph_init()
3494 ** - options parsing now in rrd_graph_options()
3495 ** - script parsing now in rrd_graph_script()
3509 rrd_graph_init(&im);
3511 /* a dummy surface so that we can measure text sizes for placements */
3512 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3513 im.cr = cairo_create(im.surface);
3514 im.graphhandle = stream;
3516 rrd_graph_options(argc, argv, &im);
3517 if (rrd_test_error()) {
3522 if (optind >= argc) {
3523 rrd_set_error("missing filename");
3527 if (strlen(argv[optind]) >= MAXPATH) {
3528 rrd_set_error("filename (including path) too long");
3533 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3534 im.graphfile[MAXPATH - 1] = '\0';
3536 rrd_graph_script(argc, argv, &im, 1);
3537 if (rrd_test_error()) {
3542 /* Everything is now read and the actual work can start */
3545 if (graph_paint(&im, prdata) == -1) {
3550 /* The image is generated and needs to be output.
3551 ** Also, if needed, print a line with information about the image.
3562 /* maybe prdata is not allocated yet ... lets do it now */
3563 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3564 rrd_set_error("malloc imginfo");
3569 malloc((strlen(im.imginfo) + 200 +
3570 strlen(im.graphfile)) * sizeof(char)))
3572 rrd_set_error("malloc imginfo");
3575 filename = im.graphfile + strlen(im.graphfile);
3576 while (filename > im.graphfile) {
3577 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3582 sprintf((*prdata)[0], im.imginfo, filename,
3583 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3589 /* a simplified version of the above that just creates the graph in memory
3590 and returns a pointer to it. */
3592 unsigned char *rrd_graph_in_memory(
3604 rrd_graph_init(&im);
3606 /* a dummy surface so that we can measure text sizes for placements */
3607 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3608 im.cr = cairo_create(im.surface);
3610 rrd_graph_options(argc, argv, &im);
3611 if (rrd_test_error()) {
3616 rrd_graph_script(argc, argv, &im, 1);
3617 if (rrd_test_error()) {
3622 /* Everything is now read and the actual work can start */
3624 /* by not assigning a name to im.graphfile data will be written to
3625 newly allocated memory on im.rendered_image ... */
3628 if (graph_paint(&im, prdata) == -1) {
3637 *img_size = im.rendered_image_size;
3640 return im.rendered_image;
3643 void rrd_graph_init(
3651 #ifdef HAVE_SETLOCALE
3652 setlocale(LC_TIME, "");
3653 #ifdef HAVE_MBSTOWCS
3654 setlocale(LC_CTYPE, "");
3660 im->xlab_user.minsec = -1;
3665 im->rendered_image_size = 0;
3666 im->rendered_image = NULL;
3668 im->ylegend[0] = '\0';
3669 im->title[0] = '\0';
3670 im->watermark[0] = '\0';
3673 im->unitsexponent = 9999;
3674 im->unitslength = 6;
3675 im->forceleftspace = 0;
3677 im->viewfactor = 1.0;
3678 im->imgformat = IF_PNG;
3679 im->graphfile[0] = '\0';
3682 im->extra_flags = 0;
3688 im->logarithmic = 0;
3689 im->ygridstep = DNAN;
3690 im->draw_x_grid = 1;
3691 im->draw_y_grid = 1;
3696 im->grid_dash_on = 1;
3697 im->grid_dash_off = 1;
3698 im->tabwidth = 40.0;
3700 im->font_options = cairo_font_options_create();
3701 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3703 cairo_font_options_set_hint_style(im->font_options,
3704 CAIRO_HINT_STYLE_FULL);
3705 cairo_font_options_set_hint_metrics(im->font_options,
3706 CAIRO_HINT_METRICS_ON);
3707 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3710 for (i = 0; i < DIM(graph_col); i++)
3711 im->graph_col[i] = graph_col[i];
3713 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3716 char rrd_win_default_font[1000];
3718 windir = getenv("windir");
3719 /* %windir% is something like D:\windows or C:\winnt */
3720 if (windir != NULL) {
3721 strncpy(rrd_win_default_font, windir, 500);
3722 rrd_win_default_font[500] = '\0';
3723 strcat(rrd_win_default_font, "\\fonts\\");
3724 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3725 for (i = 0; i < DIM(text_prop); i++) {
3726 strncpy(text_prop[i].font, rrd_win_default_font,
3727 sizeof(text_prop[i].font) - 1);
3728 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3736 deffont = getenv("RRD_DEFAULT_FONT");
3737 if (deffont != NULL) {
3738 for (i = 0; i < DIM(text_prop); i++) {
3739 strncpy(text_prop[i].font, deffont,
3740 sizeof(text_prop[i].font) - 1);
3741 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3745 for (i = 0; i < DIM(text_prop); i++) {
3746 im->text_prop[i].size = text_prop[i].size;
3747 strcpy(im->text_prop[i].font, text_prop[i].font);
3751 void rrd_graph_options(
3757 char *parsetime_error = NULL;
3758 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3759 time_t start_tmp = 0, end_tmp = 0;
3761 struct rrd_time_value start_tv, end_tv;
3762 long unsigned int color;
3763 char *old_locale = "";
3765 /* defines for long options without a short equivalent. should be bytes,
3766 and may not collide with (the ASCII value of) short options */
3767 #define LONGOPT_UNITS_SI 255
3768 struct option long_options[] = {
3769 {"start", required_argument, 0, 's'},
3770 {"end", required_argument, 0, 'e'},
3771 {"x-grid", required_argument, 0, 'x'},
3772 {"y-grid", required_argument, 0, 'y'},
3773 {"vertical-label", required_argument, 0, 'v'},
3774 {"width", required_argument, 0, 'w'},
3775 {"height", required_argument, 0, 'h'},
3776 {"full-size-mode", no_argument, 0, 'D'},
3777 {"interlaced", no_argument, 0, 'i'},
3778 {"upper-limit", required_argument, 0, 'u'},
3779 {"lower-limit", required_argument, 0, 'l'},
3780 {"rigid", no_argument, 0, 'r'},
3781 {"base", required_argument, 0, 'b'},
3782 {"logarithmic", no_argument, 0, 'o'},
3783 {"color", required_argument, 0, 'c'},
3784 {"font", required_argument, 0, 'n'},
3785 {"title", required_argument, 0, 't'},
3786 {"imginfo", required_argument, 0, 'f'},
3787 {"imgformat", required_argument, 0, 'a'},
3788 {"lazy", no_argument, 0, 'z'},
3789 {"zoom", required_argument, 0, 'm'},
3790 {"no-legend", no_argument, 0, 'g'},
3791 {"force-rules-legend", no_argument, 0, 'F'},
3792 {"only-graph", no_argument, 0, 'j'},
3793 {"alt-y-grid", no_argument, 0, 'Y'},
3794 {"no-minor", no_argument, 0, 'I'},
3795 {"slope-mode", no_argument, 0, 'E'},
3796 {"alt-autoscale", no_argument, 0, 'A'},
3797 {"alt-autoscale-min", no_argument, 0, 'J'},
3798 {"alt-autoscale-max", no_argument, 0, 'M'},
3799 {"no-gridfit", no_argument, 0, 'N'},
3800 {"units-exponent", required_argument, 0, 'X'},
3801 {"units-length", required_argument, 0, 'L'},
3802 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3803 {"step", required_argument, 0, 'S'},
3804 {"tabwidth", required_argument, 0, 'T'},
3805 {"font-render-mode", required_argument, 0, 'R'},
3806 {"graph-render-mode", required_argument, 0, 'G'},
3807 {"font-smoothing-threshold", required_argument, 0, 'B'},
3808 {"watermark", required_argument, 0, 'W'},
3809 {"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 */
3814 opterr = 0; /* initialize getopt */
3816 parsetime("end-24h", &start_tv);
3817 parsetime("now", &end_tv);
3820 int option_index = 0;
3822 int col_start, col_end;
3824 opt = getopt_long(argc, argv,
3825 "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:",
3826 long_options, &option_index);
3833 im->extra_flags |= NOMINOR;
3836 im->extra_flags |= ALTYGRID;
3839 im->extra_flags |= ALTAUTOSCALE;
3842 im->extra_flags |= ALTAUTOSCALE_MIN;
3845 im->extra_flags |= ALTAUTOSCALE_MAX;
3848 im->extra_flags |= ONLY_GRAPH;
3851 im->extra_flags |= NOLEGEND;
3854 im->extra_flags |= FORCE_RULES_LEGEND;
3856 case LONGOPT_UNITS_SI:
3857 if (im->extra_flags & FORCE_UNITS) {
3858 rrd_set_error("--units can only be used once!");
3859 setlocale(LC_NUMERIC, old_locale);
3862 if (strcmp(optarg, "si") == 0)
3863 im->extra_flags |= FORCE_UNITS_SI;
3865 rrd_set_error("invalid argument for --units: %s", optarg);
3870 im->unitsexponent = atoi(optarg);
3873 im->unitslength = atoi(optarg);
3874 im->forceleftspace = 1;
3877 old_locale = setlocale(LC_NUMERIC, "C");
3878 im->tabwidth = atof(optarg);
3879 setlocale(LC_NUMERIC, old_locale);
3882 old_locale = setlocale(LC_NUMERIC, "C");
3883 im->step = atoi(optarg);
3884 setlocale(LC_NUMERIC, old_locale);
3890 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3891 rrd_set_error("start time: %s", parsetime_error);
3896 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3897 rrd_set_error("end time: %s", parsetime_error);
3902 if (strcmp(optarg, "none") == 0) {
3903 im->draw_x_grid = 0;
3908 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3910 &im->xlab_user.gridst,
3912 &im->xlab_user.mgridst,
3914 &im->xlab_user.labst,
3915 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3916 strncpy(im->xlab_form, optarg + stroff,
3917 sizeof(im->xlab_form) - 1);
3918 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3919 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3920 rrd_set_error("unknown keyword %s", scan_gtm);
3922 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3924 rrd_set_error("unknown keyword %s", scan_mtm);
3926 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3928 rrd_set_error("unknown keyword %s", scan_ltm);
3931 im->xlab_user.minsec = 1;
3932 im->xlab_user.stst = im->xlab_form;
3934 rrd_set_error("invalid x-grid format");
3940 if (strcmp(optarg, "none") == 0) {
3941 im->draw_y_grid = 0;
3944 old_locale = setlocale(LC_NUMERIC, "C");
3945 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3946 setlocale(LC_NUMERIC, old_locale);
3947 if (im->ygridstep <= 0) {
3948 rrd_set_error("grid step must be > 0");
3950 } else if (im->ylabfact < 1) {
3951 rrd_set_error("label factor must be > 0");
3955 setlocale(LC_NUMERIC, old_locale);
3956 rrd_set_error("invalid y-grid format");
3961 strncpy(im->ylegend, optarg, 150);
3962 im->ylegend[150] = '\0';
3965 old_locale = setlocale(LC_NUMERIC, "C");
3966 im->maxval = atof(optarg);
3967 setlocale(LC_NUMERIC, old_locale);
3970 old_locale = setlocale(LC_NUMERIC, "C");
3971 im->minval = atof(optarg);
3972 setlocale(LC_NUMERIC, old_locale);
3975 im->base = atol(optarg);
3976 if (im->base != 1024 && im->base != 1000) {
3978 ("the only sensible value for base apart from 1000 is 1024");
3983 long_tmp = atol(optarg);
3984 if (long_tmp < 10) {
3985 rrd_set_error("width below 10 pixels");
3988 im->xsize = long_tmp;
3991 long_tmp = atol(optarg);
3992 if (long_tmp < 10) {
3993 rrd_set_error("height below 10 pixels");
3996 im->ysize = long_tmp;
3999 im->extra_flags |= FULL_SIZE_MODE;
4002 /* interlaced png not supported at the moment */
4008 im->imginfo = optarg;
4011 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
4012 rrd_set_error("unsupported graphics format '%s'", optarg);
4024 im->logarithmic = 1;
4028 "%10[A-Z]#%n%8lx%n",
4029 col_nam, &col_start, &color, &col_end) == 2) {
4031 int col_len = col_end - col_start;
4035 color = (((color & 0xF00) * 0x110000) |
4036 ((color & 0x0F0) * 0x011000) |
4037 ((color & 0x00F) * 0x001100) | 0x000000FF);
4040 color = (((color & 0xF000) * 0x11000) |
4041 ((color & 0x0F00) * 0x01100) |
4042 ((color & 0x00F0) * 0x00110) |
4043 ((color & 0x000F) * 0x00011)
4047 color = (color << 8) + 0xff /* shift left by 8 */ ;
4052 rrd_set_error("the color format is #RRGGBB[AA]");
4055 if ((ci = grc_conv(col_nam)) != -1) {
4056 im->graph_col[ci] = gfx_hex_to_col(color);
4058 rrd_set_error("invalid color name '%s'", col_nam);
4062 rrd_set_error("invalid color def format");
4071 old_locale = setlocale(LC_NUMERIC, "C");
4072 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4073 int sindex, propidx;
4075 setlocale(LC_NUMERIC, old_locale);
4076 if ((sindex = text_prop_conv(prop)) != -1) {
4077 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4080 im->text_prop[propidx].size = size;
4082 if (strlen(prop) > end) {
4083 if (prop[end] == ':') {
4084 strncpy(im->text_prop[propidx].font,
4085 prop + end + 1, 255);
4086 im->text_prop[propidx].font[255] = '\0';
4089 ("expected after font size in '%s'",
4094 if (propidx == sindex && sindex != 0)
4098 rrd_set_error("invalid fonttag '%s'", prop);
4102 setlocale(LC_NUMERIC, old_locale);
4103 rrd_set_error("invalid text property format");
4109 old_locale = setlocale(LC_NUMERIC, "C");
4110 im->zoom = atof(optarg);
4111 setlocale(LC_NUMERIC, old_locale);
4112 if (im->zoom <= 0.0) {
4113 rrd_set_error("zoom factor must be > 0");
4118 strncpy(im->title, optarg, 150);
4119 im->title[150] = '\0';
4123 if (strcmp(optarg, "normal") == 0) {
4124 cairo_font_options_set_antialias(im->font_options,
4125 CAIRO_ANTIALIAS_GRAY);
4126 cairo_font_options_set_hint_style(im->font_options,
4127 CAIRO_HINT_STYLE_FULL);
4128 } else if (strcmp(optarg, "light") == 0) {
4129 cairo_font_options_set_antialias(im->font_options,
4130 CAIRO_ANTIALIAS_GRAY);
4131 cairo_font_options_set_hint_style(im->font_options,
4132 CAIRO_HINT_STYLE_SLIGHT);
4133 } else if (strcmp(optarg, "mono") == 0) {
4134 cairo_font_options_set_antialias(im->font_options,
4135 CAIRO_ANTIALIAS_NONE);
4136 cairo_font_options_set_hint_style(im->font_options,
4137 CAIRO_HINT_STYLE_FULL);
4139 rrd_set_error("unknown font-render-mode '%s'", optarg);
4144 if (strcmp(optarg, "normal") == 0)
4145 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4146 else if (strcmp(optarg, "mono") == 0)
4147 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4149 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4154 /* not supported curently */
4158 strncpy(im->watermark, optarg, 100);
4159 im->watermark[99] = '\0';
4164 rrd_set_error("unknown option '%c'", optopt);
4166 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4171 if (im->logarithmic == 1 && im->minval <= 0) {
4173 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4177 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4178 /* error string is set in parsetime.c */
4182 if (start_tmp < 3600 * 24 * 365 * 10) {
4183 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4188 if (end_tmp < start_tmp) {
4189 rrd_set_error("start (%ld) should be less than end (%ld)",
4190 start_tmp, end_tmp);
4194 im->start = start_tmp;
4196 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4199 int rrd_graph_color(
4206 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4208 color = strstr(var, "#");
4209 if (color == NULL) {
4210 if (optional == 0) {
4211 rrd_set_error("Found no color in %s", err);
4218 long unsigned int col;
4220 rest = strstr(color, ":");
4228 sscanf(color, "#%6lx%n", &col, &n);
4229 col = (col << 8) + 0xff /* shift left by 8 */ ;
4231 rrd_set_error("Color problem in %s", err);
4234 sscanf(color, "#%8lx%n", &col, &n);
4238 rrd_set_error("Color problem in %s", err);
4240 if (rrd_test_error())
4242 gdp->col = gfx_hex_to_col(col);
4255 while (*ptr != '\0')
4256 if (*ptr++ == '%') {
4258 /* line cannot end with percent char */
4262 /* '%s', '%S' and '%%' are allowed */
4263 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4266 /* %c is allowed (but use only with vdef!) */
4267 else if (*ptr == 'c') {
4272 /* or else '% 6.2lf' and such are allowed */
4274 /* optional padding character */
4275 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4278 /* This should take care of 'm.n' with all three optional */
4279 while (*ptr >= '0' && *ptr <= '9')
4283 while (*ptr >= '0' && *ptr <= '9')
4286 /* Either 'le', 'lf' or 'lg' must follow here */
4289 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4302 struct graph_desc_t *gdes,
4303 const char *const str)
4305 /* A VDEF currently is either "func" or "param,func"
4306 * so the parsing is rather simple. Change if needed.
4314 old_locale = setlocale(LC_NUMERIC, "C");
4315 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4316 setlocale(LC_NUMERIC, old_locale);
4317 if (n == (int) strlen(str)) { /* matched */
4321 sscanf(str, "%29[A-Z]%n", func, &n);
4322 if (n == (int) strlen(str)) { /* matched */
4325 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4330 if (!strcmp("PERCENT", func))
4331 gdes->vf.op = VDEF_PERCENT;
4332 else if (!strcmp("MAXIMUM", func))
4333 gdes->vf.op = VDEF_MAXIMUM;
4334 else if (!strcmp("AVERAGE", func))
4335 gdes->vf.op = VDEF_AVERAGE;
4336 else if (!strcmp("STDEV", func))
4337 gdes->vf.op = VDEF_STDEV;
4338 else if (!strcmp("MINIMUM", func))
4339 gdes->vf.op = VDEF_MINIMUM;
4340 else if (!strcmp("TOTAL", func))
4341 gdes->vf.op = VDEF_TOTAL;
4342 else if (!strcmp("FIRST", func))
4343 gdes->vf.op = VDEF_FIRST;
4344 else if (!strcmp("LAST", func))
4345 gdes->vf.op = VDEF_LAST;
4346 else if (!strcmp("LSLSLOPE", func))
4347 gdes->vf.op = VDEF_LSLSLOPE;
4348 else if (!strcmp("LSLINT", func))
4349 gdes->vf.op = VDEF_LSLINT;
4350 else if (!strcmp("LSLCORREL", func))
4351 gdes->vf.op = VDEF_LSLCORREL;
4353 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4358 switch (gdes->vf.op) {
4360 if (isnan(param)) { /* no parameter given */
4361 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4365 if (param >= 0.0 && param <= 100.0) {
4366 gdes->vf.param = param;
4367 gdes->vf.val = DNAN; /* undefined */
4368 gdes->vf.when = 0; /* undefined */
4370 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4384 case VDEF_LSLCORREL:
4386 gdes->vf.param = DNAN;
4387 gdes->vf.val = DNAN;
4390 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4404 graph_desc_t *src, *dst;
4408 dst = &im->gdes[gdi];
4409 src = &im->gdes[dst->vidx];
4410 data = src->data + src->ds;
4411 steps = (src->end - src->start) / src->step;
4414 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4418 switch (dst->vf.op) {
4424 if ((array = malloc(steps * sizeof(double))) == NULL) {
4425 rrd_set_error("malloc VDEV_PERCENT");
4428 for (step = 0; step < steps; step++) {
4429 array[step] = data[step * src->ds_cnt];
4431 qsort(array, step, sizeof(double), vdef_percent_compar);
4433 field = (steps - 1) * dst->vf.param / 100;
4434 dst->vf.val = array[field];
4435 dst->vf.when = 0; /* no time component */
4438 for (step = 0; step < steps; step++)
4439 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4440 step == field ? '*' : ' ');
4446 while (step != steps && isnan(data[step * src->ds_cnt]))
4448 if (step == steps) {
4452 dst->vf.val = data[step * src->ds_cnt];
4453 dst->vf.when = src->start + (step + 1) * src->step;
4455 while (step != steps) {
4456 if (finite(data[step * src->ds_cnt])) {
4457 if (data[step * src->ds_cnt] > dst->vf.val) {
4458 dst->vf.val = data[step * src->ds_cnt];
4459 dst->vf.when = src->start + (step + 1) * src->step;
4470 double average = 0.0;
4472 for (step = 0; step < steps; step++) {
4473 if (finite(data[step * src->ds_cnt])) {
4474 sum += data[step * src->ds_cnt];
4479 if (dst->vf.op == VDEF_TOTAL) {
4480 dst->vf.val = sum * src->step;
4481 dst->vf.when = 0; /* no time component */
4482 } else if (dst->vf.op == VDEF_AVERAGE) {
4483 dst->vf.val = sum / cnt;
4484 dst->vf.when = 0; /* no time component */
4486 average = sum / cnt;
4488 for (step = 0; step < steps; step++) {
4489 if (finite(data[step * src->ds_cnt])) {
4490 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4493 dst->vf.val = pow(sum / cnt, 0.5);
4494 dst->vf.when = 0; /* no time component */
4504 while (step != steps && isnan(data[step * src->ds_cnt]))
4506 if (step == steps) {
4510 dst->vf.val = data[step * src->ds_cnt];
4511 dst->vf.when = src->start + (step + 1) * src->step;
4513 while (step != steps) {
4514 if (finite(data[step * src->ds_cnt])) {
4515 if (data[step * src->ds_cnt] < dst->vf.val) {
4516 dst->vf.val = data[step * src->ds_cnt];
4517 dst->vf.when = src->start + (step + 1) * src->step;
4524 /* The time value returned here is one step before the
4525 * actual time value. This is the start of the first
4529 while (step != steps && isnan(data[step * src->ds_cnt]))
4531 if (step == steps) { /* all entries were NaN */
4535 dst->vf.val = data[step * src->ds_cnt];
4536 dst->vf.when = src->start + step * src->step;
4540 /* The time value returned here is the
4541 * actual time value. This is the end of the last
4545 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4547 if (step < 0) { /* all entries were NaN */
4551 dst->vf.val = data[step * src->ds_cnt];
4552 dst->vf.when = src->start + (step + 1) * src->step;
4557 case VDEF_LSLCORREL:{
4558 /* Bestfit line by linear least squares method */
4561 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4569 for (step = 0; step < steps; step++) {
4570 if (finite(data[step * src->ds_cnt])) {
4573 SUMxx += step * step;
4574 SUMxy += step * data[step * src->ds_cnt];
4575 SUMy += data[step * src->ds_cnt];
4576 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4580 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4581 y_intercept = (SUMy - slope * SUMx) / cnt;
4584 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4585 (SUMx * SUMx) / cnt) * (SUMyy -
4591 if (dst->vf.op == VDEF_LSLSLOPE) {
4592 dst->vf.val = slope;
4594 } else if (dst->vf.op == VDEF_LSLINT) {
4595 dst->vf.val = y_intercept;
4597 } else if (dst->vf.op == VDEF_LSLCORREL) {
4598 dst->vf.val = correl;
4612 /* NaN < -INF < finite_values < INF */
4613 int vdef_percent_compar(
4617 /* Equality is not returned; this doesn't hurt except
4618 * (maybe) for a little performance.
4621 /* First catch NaN values. They are smallest */
4622 if (isnan(*(double *) a))
4624 if (isnan(*(double *) b))
4627 /* NaN doesn't reach this part so INF and -INF are extremes.
4628 * The sign from isinf() is compatible with the sign we return
4630 if (isinf(*(double *) a))
4631 return isinf(*(double *) a);
4632 if (isinf(*(double *) b))
4633 return isinf(*(double *) b);
4635 /* If we reach this, both values must be finite */
4636 if (*(double *) a < *(double *) b)