1 /****************************************************************************
2 * RRDtool 1.4.3 Copyright by Tobi Oetiker, 1997-2010
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
20 #include "plbasename.h"
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
32 #ifdef HAVE_LANGINFO_H
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
48 text_prop_t text_prop[] = {
49 {8.0, RRD_DEFAULT_FONT,NULL}
51 {9.0, RRD_DEFAULT_FONT,NULL}
53 {7.0, RRD_DEFAULT_FONT,NULL}
55 {8.0, RRD_DEFAULT_FONT,NULL}
57 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
59 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
62 char week_fmt[128] = "Week %V";
65 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
67 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
69 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
71 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
73 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
75 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
77 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
79 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
81 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
83 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
84 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
86 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
88 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
90 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
92 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, week_fmt}
94 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
97 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
100 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
103 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
105 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
106 365 * 24 * 3600, "%y"}
108 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
111 /* sensible y label intervals ...*/
135 {20.0, {1, 5, 10, 20}
141 {100.0, {1, 2, 5, 10}
144 {200.0, {1, 5, 10, 20}
147 {500.0, {1, 2, 4, 10}
155 gfx_color_t graph_col[] = /* default colors */
157 {1.00, 1.00, 1.00, 1.00}, /* canvas */
158 {0.95, 0.95, 0.95, 1.00}, /* background */
159 {0.81, 0.81, 0.81, 1.00}, /* shade A */
160 {0.62, 0.62, 0.62, 1.00}, /* shade B */
161 {0.56, 0.56, 0.56, 0.75}, /* grid */
162 {0.87, 0.31, 0.31, 0.60}, /* major grid */
163 {0.00, 0.00, 0.00, 1.00}, /* font */
164 {0.50, 0.12, 0.12, 1.00}, /* arrow */
165 {0.12, 0.12, 0.12, 1.00}, /* axis */
166 {0.00, 0.00, 0.00, 1.00} /* frame */
173 # define DPRINT(x) (void)(printf x, printf("\n"))
179 /* initialize with xtr(im,0); */
187 pixie = (double) im->xsize / (double) (im->end - im->start);
190 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
193 /* translate data values into y coordinates */
202 if (!im->logarithmic)
203 pixie = (double) im->ysize / (im->maxval - im->minval);
206 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
208 } else if (!im->logarithmic) {
209 yval = im->yorigin - pixie * (value - im->minval);
211 if (value < im->minval) {
214 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
222 /* conversion function for symbolic entry names */
225 #define conv_if(VV,VVV) \
226 if (strcmp(#VV, string) == 0) return VVV ;
232 conv_if(PRINT, GF_PRINT);
233 conv_if(GPRINT, GF_GPRINT);
234 conv_if(COMMENT, GF_COMMENT);
235 conv_if(HRULE, GF_HRULE);
236 conv_if(VRULE, GF_VRULE);
237 conv_if(LINE, GF_LINE);
238 conv_if(AREA, GF_AREA);
239 conv_if(GRAD, GF_GRAD);
240 conv_if(STACK, GF_STACK);
241 conv_if(TICK, GF_TICK);
242 conv_if(TEXTALIGN, GF_TEXTALIGN);
243 conv_if(DEF, GF_DEF);
244 conv_if(CDEF, GF_CDEF);
245 conv_if(VDEF, GF_VDEF);
246 conv_if(XPORT, GF_XPORT);
247 conv_if(SHIFT, GF_SHIFT);
249 return (enum gf_en)(-1);
252 enum gfx_if_en if_conv(
256 conv_if(PNG, IF_PNG);
257 conv_if(SVG, IF_SVG);
258 conv_if(EPS, IF_EPS);
259 conv_if(PDF, IF_PDF);
261 return (enum gfx_if_en)(-1);
264 enum tmt_en tmt_conv(
268 conv_if(SECOND, TMT_SECOND);
269 conv_if(MINUTE, TMT_MINUTE);
270 conv_if(HOUR, TMT_HOUR);
271 conv_if(DAY, TMT_DAY);
272 conv_if(WEEK, TMT_WEEK);
273 conv_if(MONTH, TMT_MONTH);
274 conv_if(YEAR, TMT_YEAR);
275 return (enum tmt_en)(-1);
278 enum grc_en grc_conv(
282 conv_if(BACK, GRC_BACK);
283 conv_if(CANVAS, GRC_CANVAS);
284 conv_if(SHADEA, GRC_SHADEA);
285 conv_if(SHADEB, GRC_SHADEB);
286 conv_if(GRID, GRC_GRID);
287 conv_if(MGRID, GRC_MGRID);
288 conv_if(FONT, GRC_FONT);
289 conv_if(ARROW, GRC_ARROW);
290 conv_if(AXIS, GRC_AXIS);
291 conv_if(FRAME, GRC_FRAME);
293 return (enum grc_en)(-1);
296 enum text_prop_en text_prop_conv(
300 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
301 conv_if(TITLE, TEXT_PROP_TITLE);
302 conv_if(AXIS, TEXT_PROP_AXIS);
303 conv_if(UNIT, TEXT_PROP_UNIT);
304 conv_if(LEGEND, TEXT_PROP_LEGEND);
305 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
306 return (enum text_prop_en)(-1);
316 cairo_status_t status = (cairo_status_t) 0;
321 if (im->daemon_addr != NULL)
322 free(im->daemon_addr);
324 for (i = 0; i < (unsigned) im->gdes_c; i++) {
325 if (im->gdes[i].data_first) {
326 /* careful here, because a single pointer can occur several times */
327 free(im->gdes[i].data);
328 if (im->gdes[i].ds_namv) {
329 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
330 free(im->gdes[i].ds_namv[ii]);
331 free(im->gdes[i].ds_namv);
334 /* free allocated memory used for dashed lines */
335 if (im->gdes[i].p_dashes != NULL)
336 free(im->gdes[i].p_dashes);
338 free(im->gdes[i].p_data);
339 free(im->gdes[i].rpnp);
343 for (i = 0; i < DIM(text_prop);i++){
344 pango_font_description_free(im->text_prop[i].font_desc);
345 im->text_prop[i].font_desc = NULL;
348 if (im->font_options)
349 cairo_font_options_destroy(im->font_options);
352 status = cairo_status(im->cr);
353 cairo_destroy(im->cr);
357 if (im->rendered_image) {
358 free(im->rendered_image);
362 g_object_unref (im->layout);
366 cairo_surface_destroy(im->surface);
369 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
370 cairo_status_to_string(status));
375 /* find SI magnitude symbol for the given number*/
377 image_desc_t *im, /* image description */
383 char *symbol[] = { "a", /* 10e-18 Atto */
384 "f", /* 10e-15 Femto */
385 "p", /* 10e-12 Pico */
386 "n", /* 10e-9 Nano */
387 "u", /* 10e-6 Micro */
388 "m", /* 10e-3 Milli */
393 "T", /* 10e12 Tera */
394 "P", /* 10e15 Peta */
401 if (*value == 0.0 || isnan(*value)) {
405 sindex = floor(log(fabs(*value)) / log((double) im->base));
406 *magfact = pow((double) im->base, (double) sindex);
407 (*value) /= (*magfact);
409 if (sindex <= symbcenter && sindex >= -symbcenter) {
410 (*symb_ptr) = symbol[sindex + symbcenter];
417 static char si_symbol[] = {
418 'a', /* 10e-18 Atto */
419 'f', /* 10e-15 Femto */
420 'p', /* 10e-12 Pico */
421 'n', /* 10e-9 Nano */
422 'u', /* 10e-6 Micro */
423 'm', /* 10e-3 Milli */
428 'T', /* 10e12 Tera */
429 'P', /* 10e15 Peta */
432 static const int si_symbcenter = 6;
434 /* find SI magnitude symbol for the numbers on the y-axis*/
436 image_desc_t *im /* image description */
440 double digits, viewdigits = 0;
443 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
444 log((double) im->base));
446 if (im->unitsexponent != 9999) {
447 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
448 viewdigits = floor((double)(im->unitsexponent / 3));
453 im->magfact = pow((double) im->base, digits);
456 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
459 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
461 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
462 ((viewdigits + si_symbcenter) >= 0))
463 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
468 /* move min and max values around to become sensible */
473 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
474 600.0, 500.0, 400.0, 300.0, 250.0,
475 200.0, 125.0, 100.0, 90.0, 80.0,
476 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
477 25.0, 20.0, 10.0, 9.0, 8.0,
478 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
479 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
480 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
483 double scaled_min, scaled_max;
490 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
491 im->minval, im->maxval, im->magfact);
494 if (isnan(im->ygridstep)) {
495 if (im->extra_flags & ALTAUTOSCALE) {
496 /* measure the amplitude of the function. Make sure that
497 graph boundaries are slightly higher then max/min vals
498 so we can see amplitude on the graph */
501 delt = im->maxval - im->minval;
503 fact = 2.0 * pow(10.0,
505 (max(fabs(im->minval), fabs(im->maxval)) /
508 adj = (fact - delt) * 0.55;
511 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
512 im->minval, im->maxval, delt, fact, adj);
517 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
518 /* measure the amplitude of the function. Make sure that
519 graph boundaries are slightly lower than min vals
520 so we can see amplitude on the graph */
521 adj = (im->maxval - im->minval) * 0.1;
523 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
524 /* measure the amplitude of the function. Make sure that
525 graph boundaries are slightly higher than max vals
526 so we can see amplitude on the graph */
527 adj = (im->maxval - im->minval) * 0.1;
530 scaled_min = im->minval / im->magfact;
531 scaled_max = im->maxval / im->magfact;
533 for (i = 1; sensiblevalues[i] > 0; i++) {
534 if (sensiblevalues[i - 1] >= scaled_min &&
535 sensiblevalues[i] <= scaled_min)
536 im->minval = sensiblevalues[i] * (im->magfact);
538 if (-sensiblevalues[i - 1] <= scaled_min &&
539 -sensiblevalues[i] >= scaled_min)
540 im->minval = -sensiblevalues[i - 1] * (im->magfact);
542 if (sensiblevalues[i - 1] >= scaled_max &&
543 sensiblevalues[i] <= scaled_max)
544 im->maxval = sensiblevalues[i - 1] * (im->magfact);
546 if (-sensiblevalues[i - 1] <= scaled_max &&
547 -sensiblevalues[i] >= scaled_max)
548 im->maxval = -sensiblevalues[i] * (im->magfact);
552 /* adjust min and max to the grid definition if there is one */
553 im->minval = (double) im->ylabfact * im->ygridstep *
554 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
555 im->maxval = (double) im->ylabfact * im->ygridstep *
556 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
560 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
561 im->minval, im->maxval, im->magfact);
569 if (isnan(im->minval) || isnan(im->maxval))
572 if (im->logarithmic) {
573 double ya, yb, ypix, ypixfrac;
574 double log10_range = log10(im->maxval) - log10(im->minval);
576 ya = pow((double) 10, floor(log10(im->minval)));
577 while (ya < im->minval)
580 return; /* don't have y=10^x gridline */
582 if (yb <= im->maxval) {
583 /* we have at least 2 y=10^x gridlines.
584 Make sure distance between them in pixels
585 are an integer by expanding im->maxval */
586 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
587 double factor = y_pixel_delta / floor(y_pixel_delta);
588 double new_log10_range = factor * log10_range;
589 double new_ymax_log10 = log10(im->minval) + new_log10_range;
591 im->maxval = pow(10, new_ymax_log10);
592 ytr(im, DNAN); /* reset precalc */
593 log10_range = log10(im->maxval) - log10(im->minval);
595 /* make sure first y=10^x gridline is located on
596 integer pixel position by moving scale slightly
597 downwards (sub-pixel movement) */
598 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
599 ypixfrac = ypix - floor(ypix);
600 if (ypixfrac > 0 && ypixfrac < 1) {
601 double yfrac = ypixfrac / im->ysize;
603 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
604 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
605 ytr(im, DNAN); /* reset precalc */
608 /* Make sure we have an integer pixel distance between
609 each minor gridline */
610 double ypos1 = ytr(im, im->minval);
611 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
612 double y_pixel_delta = ypos1 - ypos2;
613 double factor = y_pixel_delta / floor(y_pixel_delta);
614 double new_range = factor * (im->maxval - im->minval);
615 double gridstep = im->ygrid_scale.gridstep;
616 double minor_y, minor_y_px, minor_y_px_frac;
618 if (im->maxval > 0.0)
619 im->maxval = im->minval + new_range;
621 im->minval = im->maxval - new_range;
622 ytr(im, DNAN); /* reset precalc */
623 /* make sure first minor gridline is on integer pixel y coord */
624 minor_y = gridstep * floor(im->minval / gridstep);
625 while (minor_y < im->minval)
627 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
628 minor_y_px_frac = minor_y_px - floor(minor_y_px);
629 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
630 double yfrac = minor_y_px_frac / im->ysize;
631 double range = im->maxval - im->minval;
633 im->minval = im->minval - yfrac * range;
634 im->maxval = im->maxval - yfrac * range;
635 ytr(im, DNAN); /* reset precalc */
637 calc_horizontal_grid(im); /* recalc with changed im->maxval */
641 /* reduce data reimplementation by Alex */
644 enum cf_en cf, /* which consolidation function ? */
645 unsigned long cur_step, /* step the data currently is in */
646 time_t *start, /* start, end and step as requested ... */
647 time_t *end, /* ... by the application will be ... */
648 unsigned long *step, /* ... adjusted to represent reality */
649 unsigned long *ds_cnt, /* number of data sources in file */
651 { /* two dimensional array containing the data */
652 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
653 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
655 rrd_value_t *srcptr, *dstptr;
657 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
660 row_cnt = ((*end) - (*start)) / cur_step;
666 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
667 row_cnt, reduce_factor, *start, *end, cur_step);
668 for (col = 0; col < row_cnt; col++) {
669 printf("time %10lu: ", *start + (col + 1) * cur_step);
670 for (i = 0; i < *ds_cnt; i++)
671 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
676 /* We have to combine [reduce_factor] rows of the source
677 ** into one row for the destination. Doing this we also
678 ** need to take care to combine the correct rows. First
679 ** alter the start and end time so that they are multiples
680 ** of the new step time. We cannot reduce the amount of
681 ** time so we have to move the end towards the future and
682 ** the start towards the past.
684 end_offset = (*end) % (*step);
685 start_offset = (*start) % (*step);
687 /* If there is a start offset (which cannot be more than
688 ** one destination row), skip the appropriate number of
689 ** source rows and one destination row. The appropriate
690 ** number is what we do know (start_offset/cur_step) of
691 ** the new interval (*step/cur_step aka reduce_factor).
694 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
695 printf("row_cnt before: %lu\n", row_cnt);
698 (*start) = (*start) - start_offset;
699 skiprows = reduce_factor - start_offset / cur_step;
700 srcptr += skiprows * *ds_cnt;
701 for (col = 0; col < (*ds_cnt); col++)
706 printf("row_cnt between: %lu\n", row_cnt);
709 /* At the end we have some rows that are not going to be
710 ** used, the amount is end_offset/cur_step
713 (*end) = (*end) - end_offset + (*step);
714 skiprows = end_offset / cur_step;
718 printf("row_cnt after: %lu\n", row_cnt);
721 /* Sanity check: row_cnt should be multiple of reduce_factor */
722 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
724 if (row_cnt % reduce_factor) {
725 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
726 row_cnt, reduce_factor);
727 printf("BUG in reduce_data()\n");
731 /* Now combine reduce_factor intervals at a time
732 ** into one interval for the destination.
735 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
736 for (col = 0; col < (*ds_cnt); col++) {
737 rrd_value_t newval = DNAN;
738 unsigned long validval = 0;
740 for (i = 0; i < reduce_factor; i++) {
741 if (isnan(srcptr[i * (*ds_cnt) + col])) {
746 newval = srcptr[i * (*ds_cnt) + col];
755 newval += srcptr[i * (*ds_cnt) + col];
758 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
761 /* an interval contains a failure if any subintervals contained a failure */
763 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
766 newval = srcptr[i * (*ds_cnt) + col];
792 srcptr += (*ds_cnt) * reduce_factor;
793 row_cnt -= reduce_factor;
795 /* If we had to alter the endtime, we didn't have enough
796 ** source rows to fill the last row. Fill it with NaN.
799 for (col = 0; col < (*ds_cnt); col++)
802 row_cnt = ((*end) - (*start)) / *step;
804 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
805 row_cnt, *start, *end, *step);
806 for (col = 0; col < row_cnt; col++) {
807 printf("time %10lu: ", *start + (col + 1) * (*step));
808 for (i = 0; i < *ds_cnt; i++)
809 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
816 /* get the data required for the graphs from the
825 /* pull the data from the rrd files ... */
826 for (i = 0; i < (int) im->gdes_c; i++) {
827 /* only GF_DEF elements fetch data */
828 if (im->gdes[i].gf != GF_DEF)
832 /* do we have it already ? */
833 for (ii = 0; ii < i; ii++) {
834 if (im->gdes[ii].gf != GF_DEF)
836 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
837 && (im->gdes[i].cf == im->gdes[ii].cf)
838 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
839 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
840 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
841 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
842 /* OK, the data is already there.
843 ** Just copy the header portion
845 im->gdes[i].start = im->gdes[ii].start;
846 im->gdes[i].end = im->gdes[ii].end;
847 im->gdes[i].step = im->gdes[ii].step;
848 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
849 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
850 im->gdes[i].data = im->gdes[ii].data;
851 im->gdes[i].data_first = 0;
858 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
859 const char *rrd_daemon;
862 if (im->gdes[i].daemon[0] != 0)
863 rrd_daemon = im->gdes[i].daemon;
865 rrd_daemon = im->daemon_addr;
867 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
868 * case. If "daemon" holds the same value as in the previous
869 * iteration, no actual new connection is established - the
870 * existing connection is re-used. */
871 rrdc_connect (rrd_daemon);
873 /* If connecting was successfull, use the daemon to query the data.
874 * If there is no connection, for example because no daemon address
875 * was specified, (try to) use the local file directly. */
876 if (rrdc_is_connected (rrd_daemon))
878 status = rrdc_fetch (im->gdes[i].rrd,
879 cf_to_string (im->gdes[i].cf),
884 &im->gdes[i].ds_namv,
891 if ((rrd_fetch_fn(im->gdes[i].rrd,
897 &im->gdes[i].ds_namv,
898 &im->gdes[i].data)) == -1) {
902 im->gdes[i].data_first = 1;
904 if (ft_step < im->gdes[i].step) {
905 reduce_data(im->gdes[i].cf_reduce,
910 &im->gdes[i].ds_cnt, &im->gdes[i].data);
912 im->gdes[i].step = ft_step;
916 /* lets see if the required data source is really there */
917 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
918 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
922 if (im->gdes[i].ds == -1) {
923 rrd_set_error("No DS called '%s' in '%s'",
924 im->gdes[i].ds_nam, im->gdes[i].rrd);
932 /* evaluate the expressions in the CDEF functions */
934 /*************************************************************
936 *************************************************************/
938 long find_var_wrapper(
942 return find_var((image_desc_t *) arg1, key);
945 /* find gdes containing var*/
952 for (ii = 0; ii < im->gdes_c - 1; ii++) {
953 if ((im->gdes[ii].gf == GF_DEF
954 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
955 && (strcmp(im->gdes[ii].vname, key) == 0)) {
962 /* find the greatest common divisor for all the numbers
963 in the 0 terminated num array */
970 for (i = 0; num[i + 1] != 0; i++) {
972 rest = num[i] % num[i + 1];
978 /* return i==0?num[i]:num[i-1]; */
982 /* run the rpn calculator on all the VDEF and CDEF arguments */
989 long *steparray, rpi;
994 rpnstack_init(&rpnstack);
996 for (gdi = 0; gdi < im->gdes_c; gdi++) {
997 /* Look for GF_VDEF and GF_CDEF in the same loop,
998 * so CDEFs can use VDEFs and vice versa
1000 switch (im->gdes[gdi].gf) {
1004 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1006 /* remove current shift */
1007 vdp->start -= vdp->shift;
1008 vdp->end -= vdp->shift;
1011 if (im->gdes[gdi].shidx >= 0)
1012 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1015 vdp->shift = im->gdes[gdi].shval;
1017 /* normalize shift to multiple of consolidated step */
1018 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1021 vdp->start += vdp->shift;
1022 vdp->end += vdp->shift;
1026 /* A VDEF has no DS. This also signals other parts
1027 * of rrdtool that this is a VDEF value, not a CDEF.
1029 im->gdes[gdi].ds_cnt = 0;
1030 if (vdef_calc(im, gdi)) {
1031 rrd_set_error("Error processing VDEF '%s'",
1032 im->gdes[gdi].vname);
1033 rpnstack_free(&rpnstack);
1038 im->gdes[gdi].ds_cnt = 1;
1039 im->gdes[gdi].ds = 0;
1040 im->gdes[gdi].data_first = 1;
1041 im->gdes[gdi].start = 0;
1042 im->gdes[gdi].end = 0;
1047 /* Find the variables in the expression.
1048 * - VDEF variables are substituted by their values
1049 * and the opcode is changed into OP_NUMBER.
1050 * - CDEF variables are analized for their step size,
1051 * the lowest common denominator of all the step
1052 * sizes of the data sources involved is calculated
1053 * and the resulting number is the step size for the
1054 * resulting data source.
1056 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1057 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1058 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1059 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1061 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1064 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1065 im->gdes[gdi].vname, im->gdes[ptr].vname);
1066 printf("DEBUG: value from vdef is %f\n",
1067 im->gdes[ptr].vf.val);
1069 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1070 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1071 } else { /* normal variables and PREF(variables) */
1073 /* add one entry to the array that keeps track of the step sizes of the
1074 * data sources going into the CDEF. */
1076 (long*)rrd_realloc(steparray,
1078 1) * sizeof(*steparray))) == NULL) {
1079 rrd_set_error("realloc steparray");
1080 rpnstack_free(&rpnstack);
1084 steparray[stepcnt - 1] = im->gdes[ptr].step;
1086 /* adjust start and end of cdef (gdi) so
1087 * that it runs from the latest start point
1088 * to the earliest endpoint of any of the
1089 * rras involved (ptr)
1092 if (im->gdes[gdi].start < im->gdes[ptr].start)
1093 im->gdes[gdi].start = im->gdes[ptr].start;
1095 if (im->gdes[gdi].end == 0 ||
1096 im->gdes[gdi].end > im->gdes[ptr].end)
1097 im->gdes[gdi].end = im->gdes[ptr].end;
1099 /* store pointer to the first element of
1100 * the rra providing data for variable,
1101 * further save step size and data source
1104 im->gdes[gdi].rpnp[rpi].data =
1105 im->gdes[ptr].data + im->gdes[ptr].ds;
1106 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1107 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1109 /* backoff the *.data ptr; this is done so
1110 * rpncalc() function doesn't have to treat
1111 * the first case differently
1113 } /* if ds_cnt != 0 */
1114 } /* if OP_VARIABLE */
1115 } /* loop through all rpi */
1117 /* move the data pointers to the correct period */
1118 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1119 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1120 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1121 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1123 im->gdes[gdi].start - im->gdes[ptr].start;
1126 im->gdes[gdi].rpnp[rpi].data +=
1127 (diff / im->gdes[ptr].step) *
1128 im->gdes[ptr].ds_cnt;
1132 if (steparray == NULL) {
1133 rrd_set_error("rpn expressions without DEF"
1134 " or CDEF variables are not supported");
1135 rpnstack_free(&rpnstack);
1138 steparray[stepcnt] = 0;
1139 /* Now find the resulting step. All steps in all
1140 * used RRAs have to be visited
1142 im->gdes[gdi].step = lcd(steparray);
1144 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1145 im->gdes[gdi].start)
1146 / im->gdes[gdi].step)
1147 * sizeof(double))) == NULL) {
1148 rrd_set_error("malloc im->gdes[gdi].data");
1149 rpnstack_free(&rpnstack);
1153 /* Step through the new cdef results array and
1154 * calculate the values
1156 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1157 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1158 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1160 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1161 * in this case we are advancing by timesteps;
1162 * we use the fact that time_t is a synonym for long
1164 if (rpn_calc(rpnp, &rpnstack, (long) now,
1165 im->gdes[gdi].data, ++dataidx) == -1) {
1166 /* rpn_calc sets the error string */
1167 rpnstack_free(&rpnstack);
1170 } /* enumerate over time steps within a CDEF */
1175 } /* enumerate over CDEFs */
1176 rpnstack_free(&rpnstack);
1180 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1181 /* yes we are loosing precision by doing tos with floats instead of doubles
1182 but it seems more stable this way. */
1184 static int AlmostEqual2sComplement(
1190 int aInt = *(int *) &A;
1191 int bInt = *(int *) &B;
1194 /* Make sure maxUlps is non-negative and small enough that the
1195 default NAN won't compare as equal to anything. */
1197 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1199 /* Make aInt lexicographically ordered as a twos-complement int */
1202 aInt = 0x80000000l - aInt;
1204 /* Make bInt lexicographically ordered as a twos-complement int */
1207 bInt = 0x80000000l - bInt;
1209 intDiff = abs(aInt - bInt);
1211 if (intDiff <= maxUlps)
1217 /* massage data so, that we get one value for each x coordinate in the graph */
1222 double pixstep = (double) (im->end - im->start)
1223 / (double) im->xsize; /* how much time
1224 passes in one pixel */
1226 double minval = DNAN, maxval = DNAN;
1228 unsigned long gr_time;
1230 /* memory for the processed data */
1231 for (i = 0; i < im->gdes_c; i++) {
1232 if ((im->gdes[i].gf == GF_LINE)
1233 || (im->gdes[i].gf == GF_AREA)
1234 || (im->gdes[i].gf == GF_TICK)
1235 || (im->gdes[i].gf == GF_GRAD)
1237 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1238 * sizeof(rrd_value_t))) == NULL) {
1239 rrd_set_error("malloc data_proc");
1245 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1248 gr_time = im->start + pixstep * i; /* time of the current step */
1251 for (ii = 0; ii < im->gdes_c; ii++) {
1254 switch (im->gdes[ii].gf) {
1259 if (!im->gdes[ii].stack)
1261 value = im->gdes[ii].yrule;
1262 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1263 /* The time of the data doesn't necessarily match
1264 ** the time of the graph. Beware.
1266 vidx = im->gdes[ii].vidx;
1267 if (im->gdes[vidx].gf == GF_VDEF) {
1268 value = im->gdes[vidx].vf.val;
1270 if (((long int) gr_time >=
1271 (long int) im->gdes[vidx].start)
1272 && ((long int) gr_time <
1273 (long int) im->gdes[vidx].end)) {
1274 value = im->gdes[vidx].data[(unsigned long)
1280 im->gdes[vidx].step)
1281 * im->gdes[vidx].ds_cnt +
1288 if (!isnan(value)) {
1290 im->gdes[ii].p_data[i] = paintval;
1291 /* GF_TICK: the data values are not
1292 ** relevant for min and max
1294 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1295 if ((isnan(minval) || paintval < minval) &&
1296 !(im->logarithmic && paintval <= 0.0))
1298 if (isnan(maxval) || paintval > maxval)
1302 im->gdes[ii].p_data[i] = DNAN;
1307 ("STACK should already be turned into LINE or AREA here");
1316 /* if min or max have not been asigned a value this is because
1317 there was no data in the graph ... this is not good ...
1318 lets set these to dummy values then ... */
1320 if (im->logarithmic) {
1321 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1322 minval = 0.0; /* catching this right away below */
1325 /* in logarithm mode, where minval is smaller or equal
1326 to 0 make the beast just way smaller than maxval */
1328 minval = maxval / 10e8;
1331 if (isnan(minval) || isnan(maxval)) {
1337 /* adjust min and max values given by the user */
1338 /* for logscale we add something on top */
1339 if (isnan(im->minval)
1340 || ((!im->rigid) && im->minval > minval)
1342 if (im->logarithmic)
1343 im->minval = minval / 2.0;
1345 im->minval = minval;
1347 if (isnan(im->maxval)
1348 || (!im->rigid && im->maxval < maxval)
1350 if (im->logarithmic)
1351 im->maxval = maxval * 2.0;
1353 im->maxval = maxval;
1356 /* make sure min is smaller than max */
1357 if (im->minval > im->maxval) {
1359 im->minval = 0.99 * im->maxval;
1361 im->minval = 1.01 * im->maxval;
1364 /* make sure min and max are not equal */
1365 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1371 /* make sure min and max are not both zero */
1372 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1379 static int find_first_weekday(void){
1380 static int first_weekday = -1;
1381 if (first_weekday == -1){
1382 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1383 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1384 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1385 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1386 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1387 else first_weekday = 1; /* we go for a monday default */
1392 return first_weekday;
1395 /* identify the point where the first gridline, label ... gets placed */
1397 time_t find_first_time(
1398 time_t start, /* what is the initial time */
1399 enum tmt_en baseint, /* what is the basic interval */
1400 long basestep /* how many if these do we jump a time */
1405 localtime_r(&start, &tm);
1409 tm. tm_sec -= tm.tm_sec % basestep;
1414 tm. tm_min -= tm.tm_min % basestep;
1420 tm. tm_hour -= tm.tm_hour % basestep;
1424 /* we do NOT look at the basestep for this ... */
1431 /* we do NOT look at the basestep for this ... */
1435 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1437 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1438 tm. tm_mday -= 7; /* we want the *previous* week */
1446 tm. tm_mon -= tm.tm_mon % basestep;
1457 tm.tm_year + 1900) %basestep;
1463 /* identify the point where the next gridline, label ... gets placed */
1464 time_t find_next_time(
1465 time_t current, /* what is the initial time */
1466 enum tmt_en baseint, /* what is the basic interval */
1467 long basestep /* how many if these do we jump a time */
1473 localtime_r(¤t, &tm);
1477 case TMT_SECOND: limit = 7200; break;
1478 case TMT_MINUTE: limit = 120; break;
1479 case TMT_HOUR: limit = 2; break;
1480 default: limit = 2; break;
1485 tm. tm_sec += basestep;
1489 tm. tm_min += basestep;
1493 tm. tm_hour += basestep;
1497 tm. tm_mday += basestep;
1501 tm. tm_mday += 7 * basestep;
1505 tm. tm_mon += basestep;
1509 tm. tm_year += basestep;
1511 madetime = mktime(&tm);
1512 } while (madetime == -1 && limit-- >= 0); /* this is necessary to skip impossible times
1513 like the daylight saving time skips */
1519 /* calculate values required for PRINT and GPRINT functions */
1524 long i, ii, validsteps;
1527 int graphelement = 0;
1530 double magfact = -1;
1535 /* wow initializing tmvdef is quite a task :-) */
1536 time_t now = time(NULL);
1538 localtime_r(&now, &tmvdef);
1539 for (i = 0; i < im->gdes_c; i++) {
1540 vidx = im->gdes[i].vidx;
1541 switch (im->gdes[i].gf) {
1544 /* PRINT and GPRINT can now print VDEF generated values.
1545 * There's no need to do any calculations on them as these
1546 * calculations were already made.
1548 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1549 printval = im->gdes[vidx].vf.val;
1550 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1551 } else { /* need to calculate max,min,avg etcetera */
1552 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1553 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1556 for (ii = im->gdes[vidx].ds;
1557 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1558 if (!finite(im->gdes[vidx].data[ii]))
1560 if (isnan(printval)) {
1561 printval = im->gdes[vidx].data[ii];
1566 switch (im->gdes[i].cf) {
1570 case CF_DEVSEASONAL:
1574 printval += im->gdes[vidx].data[ii];
1577 printval = min(printval, im->gdes[vidx].data[ii]);
1581 printval = max(printval, im->gdes[vidx].data[ii]);
1584 printval = im->gdes[vidx].data[ii];
1587 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1588 if (validsteps > 1) {
1589 printval = (printval / validsteps);
1592 } /* prepare printval */
1594 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1595 /* Magfact is set to -1 upon entry to print_calc. If it
1596 * is still less than 0, then we need to run auto_scale.
1597 * Otherwise, put the value into the correct units. If
1598 * the value is 0, then do not set the symbol or magnification
1599 * so next the calculation will be performed again. */
1600 if (magfact < 0.0) {
1601 auto_scale(im, &printval, &si_symb, &magfact);
1602 if (printval == 0.0)
1605 printval /= magfact;
1607 *(++percent_s) = 's';
1608 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1609 auto_scale(im, &printval, &si_symb, &magfact);
1612 if (im->gdes[i].gf == GF_PRINT) {
1613 rrd_infoval_t prline;
1615 if (im->gdes[i].strftm) {
1616 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1617 if (im->gdes[vidx].vf.never == 1) {
1618 time_clean(prline.u_str, im->gdes[i].format);
1620 strftime(prline.u_str,
1621 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1623 } else if (bad_format(im->gdes[i].format)) {
1625 ("bad format for PRINT in '%s'", im->gdes[i].format);
1629 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1633 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1638 if (im->gdes[i].strftm) {
1639 if (im->gdes[vidx].vf.never == 1) {
1640 time_clean(im->gdes[i].legend, im->gdes[i].format);
1642 strftime(im->gdes[i].legend,
1643 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1646 if (bad_format(im->gdes[i].format)) {
1648 ("bad format for GPRINT in '%s'",
1649 im->gdes[i].format);
1652 #ifdef HAVE_SNPRINTF
1653 snprintf(im->gdes[i].legend,
1655 im->gdes[i].format, printval, si_symb);
1657 sprintf(im->gdes[i].legend,
1658 im->gdes[i].format, printval, si_symb);
1671 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1672 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1677 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1678 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1687 #ifdef WITH_PIECHART
1695 ("STACK should already be turned into LINE or AREA here");
1700 return graphelement;
1705 /* place legends with color spots */
1711 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1712 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1713 int fill = 0, fill_last;
1714 double legendwidth; // = im->ximg - 2 * border;
1716 double leg_x = border;
1717 int leg_y = 0; //im->yimg;
1718 int leg_y_prev = 0; // im->yimg;
1721 int i, ii, mark = 0;
1722 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1725 char saved_legend[FMT_LEG_LEN + 5];
1731 legendwidth = im->legendwidth - 2 * border;
1735 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1736 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1737 rrd_set_error("malloc for legspace");
1741 for (i = 0; i < im->gdes_c; i++) {
1742 char prt_fctn; /*special printfunctions */
1744 strcpy(saved_legend, im->gdes[i].legend);
1748 /* hide legends for rules which are not displayed */
1749 if (im->gdes[i].gf == GF_TEXTALIGN) {
1750 default_txtalign = im->gdes[i].txtalign;
1753 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1754 if (im->gdes[i].gf == GF_HRULE
1755 && (im->gdes[i].yrule <
1756 im->minval || im->gdes[i].yrule > im->maxval))
1757 im->gdes[i].legend[0] = '\0';
1758 if (im->gdes[i].gf == GF_VRULE
1759 && (im->gdes[i].xrule <
1760 im->start || im->gdes[i].xrule > im->end))
1761 im->gdes[i].legend[0] = '\0';
1764 /* turn \\t into tab */
1765 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1766 memmove(tab, tab + 1, strlen(tab));
1770 leg_cc = strlen(im->gdes[i].legend);
1771 /* is there a controle code at the end of the legend string ? */
1772 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1773 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1775 im->gdes[i].legend[leg_cc] = '\0';
1779 /* only valid control codes */
1780 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1785 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1788 ("Unknown control code at the end of '%s\\%c'",
1789 im->gdes[i].legend, prt_fctn);
1793 if (prt_fctn == 'n') {
1797 /* remove exess space from the end of the legend for \g */
1798 while (prt_fctn == 'g' &&
1799 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1801 im->gdes[i].legend[leg_cc] = '\0';
1806 /* no interleg space if string ends in \g */
1807 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1809 fill += legspace[i];
1812 gfx_get_text_width(im,
1818 im->tabwidth, im->gdes[i].legend);
1823 /* who said there was a special tag ... ? */
1824 if (prt_fctn == 'g') {
1828 if (prt_fctn == '\0') {
1829 if(calc_width && (fill > legendwidth)){
1832 if (i == im->gdes_c - 1 || fill > legendwidth) {
1833 /* just one legend item is left right or center */
1834 switch (default_txtalign) {
1849 /* is it time to place the legends ? */
1850 if (fill > legendwidth) {
1858 if (leg_c == 1 && prt_fctn == 'j') {
1863 if (prt_fctn != '\0') {
1865 if (leg_c >= 2 && prt_fctn == 'j') {
1866 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1870 if (prt_fctn == 'c')
1871 leg_x = border + (double)(legendwidth - fill) / 2.0;
1872 if (prt_fctn == 'r')
1873 leg_x = legendwidth - fill + border;
1874 for (ii = mark; ii <= i; ii++) {
1875 if (im->gdes[ii].legend[0] == '\0')
1876 continue; /* skip empty legends */
1877 im->gdes[ii].leg_x = leg_x;
1878 im->gdes[ii].leg_y = leg_y + border;
1880 (double)gfx_get_text_width(im, leg_x,
1885 im->tabwidth, im->gdes[ii].legend)
1886 +(double)legspace[ii]
1890 if (leg_x > border || prt_fctn == 's')
1891 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1892 if (prt_fctn == 's')
1893 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1894 if (prt_fctn == 'u')
1895 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1897 if(calc_width && (fill > legendwidth)){
1906 strcpy(im->gdes[i].legend, saved_legend);
1911 im->legendwidth = legendwidth + 2 * border;
1914 im->legendheight = leg_y + border * 0.6;
1921 /* create a grid on the graph. it determines what to do
1922 from the values of xsize, start and end */
1924 /* the xaxis labels are determined from the number of seconds per pixel
1925 in the requested graph */
1927 int calc_horizontal_grid(
1935 int decimals, fractionals;
1937 im->ygrid_scale.labfact = 2;
1938 range = im->maxval - im->minval;
1939 scaledrange = range / im->magfact;
1940 /* does the scale of this graph make it impossible to put lines
1941 on it? If so, give up. */
1942 if (isnan(scaledrange)) {
1946 /* find grid spaceing */
1948 if (isnan(im->ygridstep)) {
1949 if (im->extra_flags & ALTYGRID) {
1950 /* find the value with max number of digits. Get number of digits */
1953 (max(fabs(im->maxval), fabs(im->minval)) *
1954 im->viewfactor / im->magfact));
1955 if (decimals <= 0) /* everything is small. make place for zero */
1957 im->ygrid_scale.gridstep =
1959 floor(log10(range * im->viewfactor / im->magfact))) /
1960 im->viewfactor * im->magfact;
1961 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1962 im->ygrid_scale.gridstep = 0.1;
1963 /* should have at least 5 lines but no more then 15 */
1964 if (range / im->ygrid_scale.gridstep < 5
1965 && im->ygrid_scale.gridstep >= 30)
1966 im->ygrid_scale.gridstep /= 10;
1967 if (range / im->ygrid_scale.gridstep > 15)
1968 im->ygrid_scale.gridstep *= 10;
1969 if (range / im->ygrid_scale.gridstep > 5) {
1970 im->ygrid_scale.labfact = 1;
1971 if (range / im->ygrid_scale.gridstep > 8
1972 || im->ygrid_scale.gridstep <
1973 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1974 im->ygrid_scale.labfact = 2;
1976 im->ygrid_scale.gridstep /= 5;
1977 im->ygrid_scale.labfact = 5;
1981 (im->ygrid_scale.gridstep *
1982 (double) im->ygrid_scale.labfact * im->viewfactor /
1984 if (fractionals < 0) { /* small amplitude. */
1985 int len = decimals - fractionals + 1;
1987 if (im->unitslength < len + 2)
1988 im->unitslength = len + 2;
1989 sprintf(im->ygrid_scale.labfmt,
1991 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1993 int len = decimals + 1;
1995 if (im->unitslength < len + 2)
1996 im->unitslength = len + 2;
1997 sprintf(im->ygrid_scale.labfmt,
1998 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2000 } else { /* classic rrd grid */
2001 for (i = 0; ylab[i].grid > 0; i++) {
2002 pixel = im->ysize / (scaledrange / ylab[i].grid);
2008 for (i = 0; i < 4; i++) {
2009 if (pixel * ylab[gridind].lfac[i] >=
2010 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2011 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2016 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2019 im->ygrid_scale.gridstep = im->ygridstep;
2020 im->ygrid_scale.labfact = im->ylabfact;
2025 int draw_horizontal_grid(
2031 char graph_label[100];
2033 double X0 = im->xorigin;
2034 double X1 = im->xorigin + im->xsize;
2035 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2036 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2038 double second_axis_magfact = 0;
2039 char *second_axis_symb = "";
2042 im->ygrid_scale.gridstep /
2043 (double) im->magfact * (double) im->viewfactor;
2044 MaxY = scaledstep * (double) egrid;
2045 for (i = sgrid; i <= egrid; i++) {
2047 im->ygrid_scale.gridstep * i);
2049 im->ygrid_scale.gridstep * (i + 1));
2051 if (floor(Y0 + 0.5) >=
2052 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2053 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2054 with the chosen settings. Add a label if required by settings, or if
2055 there is only one label so far and the next grid line is out of bounds. */
2056 if (i % im->ygrid_scale.labfact == 0
2058 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2059 if (im->symbol == ' ') {
2060 if (im->extra_flags & ALTYGRID) {
2061 sprintf(graph_label,
2062 im->ygrid_scale.labfmt,
2063 scaledstep * (double) i);
2066 sprintf(graph_label, "%4.1f",
2067 scaledstep * (double) i);
2069 sprintf(graph_label, "%4.0f",
2070 scaledstep * (double) i);
2074 char sisym = (i == 0 ? ' ' : im->symbol);
2076 if (im->extra_flags & ALTYGRID) {
2077 sprintf(graph_label,
2078 im->ygrid_scale.labfmt,
2079 scaledstep * (double) i, sisym);
2082 sprintf(graph_label, "%4.1f %c",
2083 scaledstep * (double) i, sisym);
2085 sprintf(graph_label, "%4.0f %c",
2086 scaledstep * (double) i, sisym);
2091 if (im->second_axis_scale != 0){
2092 char graph_label_right[100];
2093 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2094 if (im->second_axis_format[0] == '\0'){
2095 if (!second_axis_magfact){
2096 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2097 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2099 sval /= second_axis_magfact;
2102 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2104 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2108 sprintf(graph_label_right,im->second_axis_format,sval);
2112 im->graph_col[GRC_FONT],
2113 im->text_prop[TEXT_PROP_AXIS].font_desc,
2114 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2115 graph_label_right );
2121 text_prop[TEXT_PROP_AXIS].
2123 im->graph_col[GRC_FONT],
2125 text_prop[TEXT_PROP_AXIS].
2128 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2129 gfx_line(im, X0 - 2, Y0, X0, Y0,
2130 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2131 gfx_line(im, X1, Y0, X1 + 2, Y0,
2132 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2133 gfx_dashed_line(im, X0 - 2, Y0,
2139 im->grid_dash_on, im->grid_dash_off);
2140 } else if (!(im->extra_flags & NOMINOR)) {
2143 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2144 gfx_line(im, X1, Y0, X1 + 2, Y0,
2145 GRIDWIDTH, im->graph_col[GRC_GRID]);
2146 gfx_dashed_line(im, X0 - 1, Y0,
2150 graph_col[GRC_GRID],
2151 im->grid_dash_on, im->grid_dash_off);
2158 /* this is frexp for base 10 */
2169 iexp = floor(log((double)fabs(x)) / log((double)10));
2170 mnt = x / pow(10.0, iexp);
2173 mnt = x / pow(10.0, iexp);
2180 /* logaritmic horizontal grid */
2181 int horizontal_log_grid(
2185 double yloglab[][10] = {
2187 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2189 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2191 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2208 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2210 int i, j, val_exp, min_exp;
2211 double nex; /* number of decades in data */
2212 double logscale; /* scale in logarithmic space */
2213 int exfrac = 1; /* decade spacing */
2214 int mid = -1; /* row in yloglab for major grid */
2215 double mspac; /* smallest major grid spacing (pixels) */
2216 int flab; /* first value in yloglab to use */
2217 double value, tmp, pre_value;
2219 char graph_label[100];
2221 nex = log10(im->maxval / im->minval);
2222 logscale = im->ysize / nex;
2223 /* major spacing for data with high dynamic range */
2224 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2231 /* major spacing for less dynamic data */
2233 /* search best row in yloglab */
2235 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2236 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2239 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2242 /* find first value in yloglab */
2244 yloglab[mid][flab] < 10
2245 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2246 if (yloglab[mid][flab] == 10.0) {
2251 if (val_exp % exfrac)
2252 val_exp += abs(-val_exp % exfrac);
2254 X1 = im->xorigin + im->xsize;
2259 value = yloglab[mid][flab] * pow(10.0, val_exp);
2260 if (AlmostEqual2sComplement(value, pre_value, 4))
2261 break; /* it seems we are not converging */
2263 Y0 = ytr(im, value);
2264 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2266 /* major grid line */
2268 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2269 gfx_line(im, X1, Y0, X1 + 2, Y0,
2270 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2271 gfx_dashed_line(im, X0 - 2, Y0,
2276 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2278 if (im->extra_flags & FORCE_UNITS_SI) {
2283 scale = floor(val_exp / 3.0);
2285 pvalue = pow(10.0, val_exp % 3);
2287 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2288 pvalue *= yloglab[mid][flab];
2289 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2290 && ((scale + si_symbcenter) >= 0))
2291 symbol = si_symbol[scale + si_symbcenter];
2294 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2296 sprintf(graph_label, "%3.0e", value);
2298 if (im->second_axis_scale != 0){
2299 char graph_label_right[100];
2300 double sval = value*im->second_axis_scale+im->second_axis_shift;
2301 if (im->second_axis_format[0] == '\0'){
2302 if (im->extra_flags & FORCE_UNITS_SI) {
2305 auto_scale(im,&sval,&symb,&mfac);
2306 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2309 sprintf(graph_label_right,"%3.0e", sval);
2313 sprintf(graph_label_right,im->second_axis_format,sval,"");
2318 im->graph_col[GRC_FONT],
2319 im->text_prop[TEXT_PROP_AXIS].font_desc,
2320 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2321 graph_label_right );
2327 text_prop[TEXT_PROP_AXIS].
2329 im->graph_col[GRC_FONT],
2331 text_prop[TEXT_PROP_AXIS].
2334 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2336 if (mid < 4 && exfrac == 1) {
2337 /* find first and last minor line behind current major line
2338 * i is the first line and j tha last */
2340 min_exp = val_exp - 1;
2341 for (i = 1; yloglab[mid][i] < 10.0; i++);
2342 i = yloglab[mid][i - 1] + 1;
2346 i = yloglab[mid][flab - 1] + 1;
2347 j = yloglab[mid][flab];
2350 /* draw minor lines below current major line */
2351 for (; i < j; i++) {
2353 value = i * pow(10.0, min_exp);
2354 if (value < im->minval)
2356 Y0 = ytr(im, value);
2357 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2362 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2363 gfx_line(im, X1, Y0, X1 + 2, Y0,
2364 GRIDWIDTH, im->graph_col[GRC_GRID]);
2365 gfx_dashed_line(im, X0 - 1, Y0,
2369 graph_col[GRC_GRID],
2370 im->grid_dash_on, im->grid_dash_off);
2372 } else if (exfrac > 1) {
2373 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2374 value = pow(10.0, i);
2375 if (value < im->minval)
2377 Y0 = ytr(im, value);
2378 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2383 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2384 gfx_line(im, X1, Y0, X1 + 2, Y0,
2385 GRIDWIDTH, im->graph_col[GRC_GRID]);
2386 gfx_dashed_line(im, X0 - 1, Y0,
2390 graph_col[GRC_GRID],
2391 im->grid_dash_on, im->grid_dash_off);
2396 if (yloglab[mid][++flab] == 10.0) {
2402 /* draw minor lines after highest major line */
2403 if (mid < 4 && exfrac == 1) {
2404 /* find first and last minor line below current major line
2405 * i is the first line and j tha last */
2407 min_exp = val_exp - 1;
2408 for (i = 1; yloglab[mid][i] < 10.0; i++);
2409 i = yloglab[mid][i - 1] + 1;
2413 i = yloglab[mid][flab - 1] + 1;
2414 j = yloglab[mid][flab];
2417 /* draw minor lines below current major line */
2418 for (; i < j; i++) {
2420 value = i * pow(10.0, min_exp);
2421 if (value < im->minval)
2423 Y0 = ytr(im, value);
2424 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2428 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2429 gfx_line(im, X1, Y0, X1 + 2, Y0,
2430 GRIDWIDTH, im->graph_col[GRC_GRID]);
2431 gfx_dashed_line(im, X0 - 1, Y0,
2435 graph_col[GRC_GRID],
2436 im->grid_dash_on, im->grid_dash_off);
2439 /* fancy minor gridlines */
2440 else if (exfrac > 1) {
2441 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2442 value = pow(10.0, i);
2443 if (value < im->minval)
2445 Y0 = ytr(im, value);
2446 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2450 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2451 gfx_line(im, X1, Y0, X1 + 2, Y0,
2452 GRIDWIDTH, im->graph_col[GRC_GRID]);
2453 gfx_dashed_line(im, X0 - 1, Y0,
2457 graph_col[GRC_GRID],
2458 im->grid_dash_on, im->grid_dash_off);
2469 int xlab_sel; /* which sort of label and grid ? */
2470 time_t ti, tilab, timajor;
2472 char graph_label[100];
2473 double X0, Y0, Y1; /* points for filled graph and more */
2476 /* the type of time grid is determined by finding
2477 the number of seconds per pixel in the graph */
2478 if (im->xlab_user.minsec == -1) {
2479 factor = (im->end - im->start) / im->xsize;
2481 while (xlab[xlab_sel + 1].minsec !=
2482 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2484 } /* pick the last one */
2485 while (xlab[xlab_sel - 1].minsec ==
2486 xlab[xlab_sel].minsec
2487 && xlab[xlab_sel].length > (im->end - im->start)) {
2489 } /* go back to the smallest size */
2490 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2491 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2492 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2493 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2494 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2495 im->xlab_user.labst = xlab[xlab_sel].labst;
2496 im->xlab_user.precis = xlab[xlab_sel].precis;
2497 im->xlab_user.stst = xlab[xlab_sel].stst;
2500 /* y coords are the same for every line ... */
2502 Y1 = im->yorigin - im->ysize;
2503 /* paint the minor grid */
2504 if (!(im->extra_flags & NOMINOR)) {
2505 for (ti = find_first_time(im->start,
2513 find_first_time(im->start,
2518 ti < im->end && ti != -1;
2520 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2522 /* are we inside the graph ? */
2523 if (ti < im->start || ti > im->end)
2525 while (timajor < ti && timajor != -1) {
2526 timajor = find_next_time(timajor,
2529 mgridtm, im->xlab_user.mgridst);
2531 if (timajor == -1) break; /* fail in case of problems with time increments */
2533 continue; /* skip as falls on major grid line */
2535 gfx_line(im, X0, Y1 - 2, X0, Y1,
2536 GRIDWIDTH, im->graph_col[GRC_GRID]);
2537 gfx_line(im, X0, Y0, X0, Y0 + 2,
2538 GRIDWIDTH, im->graph_col[GRC_GRID]);
2539 gfx_dashed_line(im, X0, Y0 + 1, X0,
2542 graph_col[GRC_GRID],
2543 im->grid_dash_on, im->grid_dash_off);
2547 /* paint the major grid */
2548 for (ti = find_first_time(im->start,
2555 ti < im->end && ti != -1;
2556 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2558 /* are we inside the graph ? */
2559 if (ti < im->start || ti > im->end)
2562 gfx_line(im, X0, Y1 - 2, X0, Y1,
2563 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2564 gfx_line(im, X0, Y0, X0, Y0 + 3,
2565 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2566 gfx_dashed_line(im, X0, Y0 + 3, X0,
2570 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2572 /* paint the labels below the graph */
2574 find_first_time(im->start -
2583 im->xlab_user.precis / 2) && ti != -1;
2584 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2586 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2587 /* are we inside the graph ? */
2588 if (tilab < im->start || tilab > im->end)
2591 localtime_r(&tilab, &tm);
2592 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2594 # error "your libc has no strftime I guess we'll abort the exercise here."
2599 im->graph_col[GRC_FONT],
2601 text_prop[TEXT_PROP_AXIS].
2604 GFX_H_CENTER, GFX_V_TOP, graph_label);
2613 /* draw x and y axis */
2614 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2615 im->xorigin+im->xsize,im->yorigin-im->ysize,
2616 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2618 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2619 im->xorigin+im->xsize,im->yorigin-im->ysize,
2620 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2622 gfx_line(im, im->xorigin - 4,
2624 im->xorigin + im->xsize +
2625 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2626 gfx_line(im, im->xorigin,
2629 im->yorigin - im->ysize -
2630 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2631 /* arrow for X and Y axis direction */
2632 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 */
2633 im->graph_col[GRC_ARROW]);
2635 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 */
2636 im->graph_col[GRC_ARROW]);
2638 if (im->second_axis_scale != 0){
2639 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2640 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2641 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2643 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2644 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2645 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2646 im->graph_col[GRC_ARROW]);
2657 double X0, Y0; /* points for filled graph and more */
2658 struct gfx_color_t water_color;
2660 if (im->draw_3d_border > 0) {
2661 /* draw 3d border */
2662 i = im->draw_3d_border;
2663 gfx_new_area(im, 0, im->yimg,
2664 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2665 gfx_add_point(im, im->ximg - i, i);
2666 gfx_add_point(im, im->ximg, 0);
2667 gfx_add_point(im, 0, 0);
2669 gfx_new_area(im, i, im->yimg - i,
2671 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2672 gfx_add_point(im, im->ximg, 0);
2673 gfx_add_point(im, im->ximg, im->yimg);
2674 gfx_add_point(im, 0, im->yimg);
2677 if (im->draw_x_grid == 1)
2679 if (im->draw_y_grid == 1) {
2680 if (im->logarithmic) {
2681 res = horizontal_log_grid(im);
2683 res = draw_horizontal_grid(im);
2686 /* dont draw horizontal grid if there is no min and max val */
2688 char *nodata = "No Data found";
2690 gfx_text(im, im->ximg / 2,
2693 im->graph_col[GRC_FONT],
2695 text_prop[TEXT_PROP_AXIS].
2698 GFX_H_CENTER, GFX_V_CENTER, nodata);
2702 /* yaxis unit description */
2703 if (im->ylegend[0] != '\0'){
2705 im->xOriginLegendY+10,
2707 im->graph_col[GRC_FONT],
2709 text_prop[TEXT_PROP_UNIT].
2712 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2715 if (im->second_axis_legend[0] != '\0'){
2717 im->xOriginLegendY2+10,
2718 im->yOriginLegendY2,
2719 im->graph_col[GRC_FONT],
2720 im->text_prop[TEXT_PROP_UNIT].font_desc,
2722 RRDGRAPH_YLEGEND_ANGLE,
2723 GFX_H_CENTER, GFX_V_CENTER,
2724 im->second_axis_legend);
2729 im->xOriginTitle, im->yOriginTitle+6,
2730 im->graph_col[GRC_FONT],
2732 text_prop[TEXT_PROP_TITLE].
2734 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2735 /* rrdtool 'logo' */
2736 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2737 water_color = im->graph_col[GRC_FONT];
2738 water_color.alpha = 0.3;
2739 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2740 gfx_text(im, xpos, 5,
2743 text_prop[TEXT_PROP_WATERMARK].
2744 font_desc, im->tabwidth,
2745 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2747 /* graph watermark */
2748 if (im->watermark[0] != '\0') {
2749 water_color = im->graph_col[GRC_FONT];
2750 water_color.alpha = 0.3;
2752 im->ximg / 2, im->yimg - 6,
2755 text_prop[TEXT_PROP_WATERMARK].
2756 font_desc, im->tabwidth, 0,
2757 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2761 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2762 for (i = 0; i < im->gdes_c; i++) {
2763 if (im->gdes[i].legend[0] == '\0')
2765 /* im->gdes[i].leg_y is the bottom of the legend */
2766 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2767 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2768 gfx_text(im, X0, Y0,
2769 im->graph_col[GRC_FONT],
2772 [TEXT_PROP_LEGEND].font_desc,
2774 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2775 /* The legend for GRAPH items starts with "M " to have
2776 enough space for the box */
2777 if (im->gdes[i].gf != GF_PRINT &&
2778 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2782 boxH = gfx_get_text_width(im, 0,
2787 im->tabwidth, "o") * 1.2;
2789 /* shift the box up a bit */
2792 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2794 cairo_new_path(im->cr);
2795 cairo_set_line_width(im->cr, 1.0);
2798 X0 + boxH, Y0 - boxV / 2,
2799 1.0, im->gdes[i].col);
2801 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2803 cairo_new_path(im->cr);
2804 cairo_set_line_width(im->cr, 1.0);
2807 X0 + boxH / 2, Y0 - boxV,
2808 1.0, im->gdes[i].col);
2810 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2812 cairo_new_path(im->cr);
2813 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2816 X0 + boxH, Y0 - boxV,
2817 im->gdes[i].linewidth, im->gdes[i].col);
2820 /* make sure transparent colors show up the same way as in the graph */
2823 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2824 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2826 gfx_new_area(im, X0, Y0 - boxV, X0,
2827 Y0, X0 + boxH, Y0, im->gdes[i].col);
2828 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2831 cairo_new_path(im->cr);
2832 cairo_set_line_width(im->cr, 1.0);
2835 gfx_line_fit(im, &X0, &Y0);
2836 gfx_line_fit(im, &X1, &Y1);
2837 cairo_move_to(im->cr, X0, Y0);
2838 cairo_line_to(im->cr, X1, Y0);
2839 cairo_line_to(im->cr, X1, Y1);
2840 cairo_line_to(im->cr, X0, Y1);
2841 cairo_close_path(im->cr);
2842 cairo_set_source_rgba(im->cr,
2843 im->graph_col[GRC_FRAME].red,
2844 im->graph_col[GRC_FRAME].green,
2845 im->graph_col[GRC_FRAME].blue,
2846 im->graph_col[GRC_FRAME].alpha);
2848 if (im->gdes[i].dash) {
2849 /* make box borders in legend dashed if the graph is dashed */
2853 cairo_set_dash(im->cr, dashes, 1, 0.0);
2855 cairo_stroke(im->cr);
2856 cairo_restore(im->cr);
2863 /*****************************************************
2864 * lazy check make sure we rely need to create this graph
2865 *****************************************************/
2872 struct stat imgstat;
2875 return 0; /* no lazy option */
2876 if (strlen(im->graphfile) == 0)
2877 return 0; /* inmemory option */
2878 if (stat(im->graphfile, &imgstat) != 0)
2879 return 0; /* can't stat */
2880 /* one pixel in the existing graph is more then what we would
2882 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2884 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2885 return 0; /* the file does not exist */
2886 switch (im->imgformat) {
2888 size = PngSize(fd, &(im->ximg), &(im->yimg));
2898 int graph_size_location(
2903 /* The actual size of the image to draw is determined from
2904 ** several sources. The size given on the command line is
2905 ** the graph area but we need more as we have to draw labels
2906 ** and other things outside the graph area. If the option
2907 ** --full-size-mode is selected the size defines the total
2908 ** image size and the size available for the graph is
2912 /** +---+-----------------------------------+
2913 ** | y |...............graph title.........|
2914 ** | +---+-------------------------------+
2918 ** | s | x | main graph area |
2923 ** | l | b +-------------------------------+
2924 ** | e | l | x axis labels |
2925 ** +---+---+-------------------------------+
2926 ** |....................legends............|
2927 ** +---------------------------------------+
2929 ** +---------------------------------------+
2932 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2933 0, Xylabel = 0, Xmain = 0, Ymain =
2934 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2936 // no legends and no the shall be plotted it's easy
2937 if (im->extra_flags & ONLY_GRAPH) {
2939 im->ximg = im->xsize;
2940 im->yimg = im->ysize;
2941 im->yorigin = im->ysize;
2946 if(im->watermark[0] != '\0') {
2947 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2950 // calculate the width of the left vertical legend
2951 if (im->ylegend[0] != '\0') {
2952 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2955 // calculate the width of the right vertical legend
2956 if (im->second_axis_legend[0] != '\0') {
2957 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2960 Xvertical2 = Xspacing;
2963 if (im->title[0] != '\0') {
2964 /* The title is placed "inbetween" two text lines so it
2965 ** automatically has some vertical spacing. The horizontal
2966 ** spacing is added here, on each side.
2968 /* if necessary, reduce the font size of the title until it fits the image width */
2969 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2972 // we have no title; get a little clearing from the top
2977 if (im->draw_x_grid) {
2978 // calculate the height of the horizontal labelling
2979 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2981 if (im->draw_y_grid || im->forceleftspace) {
2982 // calculate the width of the vertical labelling
2984 gfx_get_text_width(im, 0,
2985 im->text_prop[TEXT_PROP_AXIS].font_desc,
2986 im->tabwidth, "0") * im->unitslength;
2990 // add some space to the labelling
2991 Xylabel += Xspacing;
2993 /* If the legend is printed besides the graph the width has to be
2994 ** calculated first. Placing the legend north or south of the
2995 ** graph requires the width calculation first, so the legend is
2996 ** skipped for the moment.
2998 im->legendheight = 0;
2999 im->legendwidth = 0;
3000 if (!(im->extra_flags & NOLEGEND)) {
3001 if(im->legendposition == WEST || im->legendposition == EAST){
3002 if (leg_place(im, 1) == -1){
3008 if (im->extra_flags & FULL_SIZE_MODE) {
3010 /* The actual size of the image to draw has been determined by the user.
3011 ** The graph area is the space remaining after accounting for the legend,
3012 ** the watermark, the axis labels, and the title.
3014 im->ximg = im->xsize;
3015 im->yimg = im->ysize;
3019 /* Now calculate the total size. Insert some spacing where
3020 desired. im->xorigin and im->yorigin need to correspond
3021 with the lower left corner of the main graph area or, if
3022 this one is not set, the imaginary box surrounding the
3024 /* Initial size calculation for the main graph area */
3026 Xmain -= Xylabel;// + Xspacing;
3027 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3028 Xmain -= im->legendwidth;// + Xspacing;
3030 if (im->second_axis_scale != 0){
3033 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3037 Xmain -= Xvertical + Xvertical2;
3039 /* limit the remaining space to 0 */
3045 /* Putting the legend north or south, the height can now be calculated */
3046 if (!(im->extra_flags & NOLEGEND)) {
3047 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3048 im->legendwidth = im->ximg;
3049 if (leg_place(im, 0) == -1){
3055 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3056 Ymain -= Yxlabel + im->legendheight;
3062 /* reserve space for the title *or* some padding above the graph */
3065 /* reserve space for padding below the graph */
3066 if (im->extra_flags & NOLEGEND) {
3067 Ymain -= 0.5*Yspacing;
3070 if (im->watermark[0] != '\0') {
3071 Ymain -= Ywatermark;
3073 /* limit the remaining height to 0 */
3078 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3080 /* The actual size of the image to draw is determined from
3081 ** several sources. The size given on the command line is
3082 ** the graph area but we need more as we have to draw labels
3083 ** and other things outside the graph area.
3087 Xmain = im->xsize; // + Xspacing;
3091 im->ximg = Xmain + Xylabel;
3092 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3093 im->ximg += Xspacing;
3096 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3097 im->ximg += im->legendwidth;// + Xspacing;
3099 if (im->second_axis_scale != 0){
3100 im->ximg += Xylabel;
3103 im->ximg += Xvertical + Xvertical2;
3105 if (!(im->extra_flags & NOLEGEND)) {
3106 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3107 im->legendwidth = im->ximg;
3108 if (leg_place(im, 0) == -1){
3114 im->yimg = Ymain + Yxlabel;
3115 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3116 im->yimg += im->legendheight;
3119 /* reserve space for the title *or* some padding above the graph */
3123 im->yimg += 1.5 * Yspacing;
3125 /* reserve space for padding below the graph */
3126 if (im->extra_flags & NOLEGEND) {
3127 im->yimg += 0.5*Yspacing;
3130 if (im->watermark[0] != '\0') {
3131 im->yimg += Ywatermark;
3136 /* In case of putting the legend in west or east position the first
3137 ** legend calculation might lead to wrong positions if some items
3138 ** are not aligned on the left hand side (e.g. centered) as the
3139 ** legendwidth wight have been increased after the item was placed.
3140 ** In this case the positions have to be recalculated.
3142 if (!(im->extra_flags & NOLEGEND)) {
3143 if(im->legendposition == WEST || im->legendposition == EAST){
3144 if (leg_place(im, 0) == -1){
3150 /* After calculating all dimensions
3151 ** it is now possible to calculate
3154 switch(im->legendposition){
3156 im->xOriginTitle = (im->ximg / 2);
3157 im->yOriginTitle = 0;
3159 im->xOriginLegend = 0;
3160 im->yOriginLegend = Ytitle;
3162 im->xOriginLegendY = 0;
3163 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3165 im->xorigin = Xvertical + Xylabel;
3166 im->yorigin = Ytitle + im->legendheight + Ymain;
3168 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3169 if (im->second_axis_scale != 0){
3170 im->xOriginLegendY2 += Xylabel;
3172 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3177 im->xOriginTitle = im->legendwidth + im->xsize / 2;
3178 im->yOriginTitle = 0;
3180 im->xOriginLegend = 0;
3181 im->yOriginLegend = Ytitle;
3183 im->xOriginLegendY = im->legendwidth;
3184 im->yOriginLegendY = Ytitle + (Ymain / 2);
3186 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3187 im->yorigin = Ytitle + Ymain;
3189 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3190 if (im->second_axis_scale != 0){
3191 im->xOriginLegendY2 += Xylabel;
3193 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3198 im->xOriginTitle = im->ximg / 2;
3199 im->yOriginTitle = 0;
3201 im->xOriginLegend = 0;
3202 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3204 im->xOriginLegendY = 0;
3205 im->yOriginLegendY = Ytitle + (Ymain / 2);
3207 im->xorigin = Xvertical + Xylabel;
3208 im->yorigin = Ytitle + Ymain;
3210 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3211 if (im->second_axis_scale != 0){
3212 im->xOriginLegendY2 += Xylabel;
3214 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3219 im->xOriginTitle = im->xsize / 2;
3220 im->yOriginTitle = 0;
3222 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3223 if (im->second_axis_scale != 0){
3224 im->xOriginLegend += Xylabel;
3226 im->yOriginLegend = Ytitle;
3228 im->xOriginLegendY = 0;
3229 im->yOriginLegendY = Ytitle + (Ymain / 2);
3231 im->xorigin = Xvertical + Xylabel;
3232 im->yorigin = Ytitle + Ymain;
3234 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3235 if (im->second_axis_scale != 0){
3236 im->xOriginLegendY2 += Xylabel;
3238 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3240 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3241 im->xOriginTitle += Xspacing;
3242 im->xOriginLegend += Xspacing;
3243 im->xOriginLegendY += Xspacing;
3244 im->xorigin += Xspacing;
3245 im->xOriginLegendY2 += Xspacing;
3255 static cairo_status_t cairo_output(
3259 unsigned int length)
3261 image_desc_t *im = (image_desc_t*)closure;
3263 im->rendered_image =
3264 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3265 if (im->rendered_image == NULL)
3266 return CAIRO_STATUS_WRITE_ERROR;
3267 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3268 im->rendered_image_size += length;
3269 return CAIRO_STATUS_SUCCESS;
3272 /* draw that picture thing ... */
3277 int lazy = lazy_check(im);
3278 double areazero = 0.0;
3279 graph_desc_t *lastgdes = NULL;
3282 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3284 /* pull the data from the rrd files ... */
3285 if (data_fetch(im) == -1)
3287 /* evaluate VDEF and CDEF operations ... */
3288 if (data_calc(im) == -1)
3290 /* calculate and PRINT and GPRINT definitions. We have to do it at
3291 * this point because it will affect the length of the legends
3292 * if there are no graph elements (i==0) we stop here ...
3293 * if we are lazy, try to quit ...
3299 /* if we want and can be lazy ... quit now */
3303 /**************************************************************
3304 *** Calculating sizes and locations became a bit confusing ***
3305 *** so I moved this into a separate function. ***
3306 **************************************************************/
3307 if (graph_size_location(im, i) == -1)
3310 info.u_cnt = im->xorigin;
3311 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3312 info.u_cnt = im->yorigin - im->ysize;
3313 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3314 info.u_cnt = im->xsize;
3315 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3316 info.u_cnt = im->ysize;
3317 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3318 info.u_cnt = im->ximg;
3319 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3320 info.u_cnt = im->yimg;
3321 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3322 info.u_cnt = im->start;
3323 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3324 info.u_cnt = im->end;
3325 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3327 /* if we want and can be lazy ... quit now */
3331 /* get actual drawing data and find min and max values */
3332 if (data_proc(im) == -1)
3334 if (!im->logarithmic) {
3338 /* identify si magnitude Kilo, Mega Giga ? */
3339 if (!im->rigid && !im->logarithmic)
3340 expand_range(im); /* make sure the upper and lower limit are
3343 info.u_val = im->minval;
3344 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3345 info.u_val = im->maxval;
3346 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3349 if (!calc_horizontal_grid(im))
3354 apply_gridfit(im); */
3355 /* the actual graph is created by going through the individual
3356 graph elements and then drawing them */
3357 cairo_surface_destroy(im->surface);
3358 switch (im->imgformat) {
3361 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3362 im->ximg * im->zoom,
3363 im->yimg * im->zoom);
3367 im->surface = strlen(im->graphfile)
3368 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3369 im->yimg * im->zoom)
3370 : cairo_pdf_surface_create_for_stream
3371 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3375 im->surface = strlen(im->graphfile)
3377 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3378 im->yimg * im->zoom)
3379 : cairo_ps_surface_create_for_stream
3380 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3384 im->surface = strlen(im->graphfile)
3386 cairo_svg_surface_create(im->
3388 im->ximg * im->zoom, im->yimg * im->zoom)
3389 : cairo_svg_surface_create_for_stream
3390 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3391 cairo_svg_surface_restrict_to_version
3392 (im->surface, CAIRO_SVG_VERSION_1_1);
3395 cairo_destroy(im->cr);
3396 im->cr = cairo_create(im->surface);
3397 cairo_set_antialias(im->cr, im->graph_antialias);
3398 cairo_scale(im->cr, im->zoom, im->zoom);
3399 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3400 gfx_new_area(im, 0, 0, 0, im->yimg,
3401 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3402 gfx_add_point(im, im->ximg, 0);
3404 gfx_new_area(im, im->xorigin,
3407 im->xsize, im->yorigin,
3410 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3411 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3413 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3414 im->xsize, im->ysize + 2.0);
3416 if (im->minval > 0.0)
3417 areazero = im->minval;
3418 if (im->maxval < 0.0)
3419 areazero = im->maxval;
3420 for (i = 0; i < im->gdes_c; i++) {
3421 switch (im->gdes[i].gf) {
3435 for (ii = 0; ii < im->xsize; ii++) {
3436 if (!isnan(im->gdes[i].p_data[ii])
3437 && im->gdes[i].p_data[ii] != 0.0) {
3438 if (im->gdes[i].yrule > 0) {
3445 im->ysize, 1.0, im->gdes[i].col);
3446 } else if (im->gdes[i].yrule < 0) {
3449 im->yorigin - im->ysize - 1.0,
3451 im->yorigin - im->ysize -
3454 im->ysize, 1.0, im->gdes[i].col);
3462 rrd_value_t diffval = im->maxval - im->minval;
3463 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3464 rrd_value_t minlimit = im->minval - 9 * diffval;
3465 for (ii = 0; ii < im->xsize; ii++) {
3466 /* fix data points at oo and -oo */
3467 if (isinf(im->gdes[i].p_data[ii])) {
3468 if (im->gdes[i].p_data[ii] > 0) {
3469 im->gdes[i].p_data[ii] = im->maxval;
3471 im->gdes[i].p_data[ii] = im->minval;
3474 /* some versions of cairo go unstable when trying
3475 to draw way out of the canvas ... lets not even try */
3476 if (im->gdes[i].p_data[ii] > maxlimit) {
3477 im->gdes[i].p_data[ii] = maxlimit;
3479 if (im->gdes[i].p_data[ii] < minlimit) {
3480 im->gdes[i].p_data[ii] = minlimit;
3484 /* *******************************************************
3489 -------|--t-1--t--------------------------------
3491 if we know the value at time t was a then
3492 we draw a square from t-1 to t with the value a.
3494 ********************************************************* */
3495 if (im->gdes[i].col.alpha != 0.0) {
3496 /* GF_LINE and friend */
3497 if (im->gdes[i].gf == GF_LINE) {
3498 double last_y = 0.0;
3502 cairo_new_path(im->cr);
3503 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3504 if (im->gdes[i].dash) {
3505 cairo_set_dash(im->cr,
3506 im->gdes[i].p_dashes,
3507 im->gdes[i].ndash, im->gdes[i].offset);
3510 for (ii = 1; ii < im->xsize; ii++) {
3511 if (isnan(im->gdes[i].p_data[ii])
3512 || (im->slopemode == 1
3513 && isnan(im->gdes[i].p_data[ii - 1]))) {
3518 last_y = ytr(im, im->gdes[i].p_data[ii]);
3519 if (im->slopemode == 0) {
3520 double x = ii - 1 + im->xorigin;
3523 gfx_line_fit(im, &x, &y);
3524 cairo_move_to(im->cr, x, y);
3525 x = ii + im->xorigin;
3527 gfx_line_fit(im, &x, &y);
3528 cairo_line_to(im->cr, x, y);
3530 double x = ii - 1 + im->xorigin;
3532 ytr(im, im->gdes[i].p_data[ii - 1]);
3533 gfx_line_fit(im, &x, &y);
3534 cairo_move_to(im->cr, x, y);
3535 x = ii + im->xorigin;
3537 gfx_line_fit(im, &x, &y);
3538 cairo_line_to(im->cr, x, y);
3542 double x1 = ii + im->xorigin;
3543 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3545 if (im->slopemode == 0
3546 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3547 double x = ii - 1 + im->xorigin;
3550 gfx_line_fit(im, &x, &y);
3551 cairo_line_to(im->cr, x, y);
3554 gfx_line_fit(im, &x1, &y1);
3555 cairo_line_to(im->cr, x1, y1);
3558 cairo_set_source_rgba(im->cr,
3564 col.blue, im->gdes[i].col.alpha);
3565 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3566 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3567 cairo_stroke(im->cr);
3568 cairo_restore(im->cr);
3574 (double *) malloc(sizeof(double) * im->xsize * 2);
3576 (double *) malloc(sizeof(double) * im->xsize * 2);
3578 (double *) malloc(sizeof(double) * im->xsize * 2);
3580 (double *) malloc(sizeof(double) * im->xsize * 2);
3583 for (ii = 0; ii <= im->xsize; ii++) {
3586 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3592 AlmostEqual2sComplement(foreY
3596 AlmostEqual2sComplement(foreY
3602 if (im->gdes[i].gf != GF_GRAD) {
3607 foreY[cntI], im->gdes[i].col);
3609 lastx = foreX[cntI];
3610 lasty = foreY[cntI];
3612 while (cntI < idxI) {
3617 AlmostEqual2sComplement(foreY
3621 AlmostEqual2sComplement(foreY
3628 if (im->gdes[i].gf != GF_GRAD) {
3629 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3631 gfx_add_rect_fadey(im,
3633 foreX[cntI], foreY[cntI], lasty,
3636 im->gdes[i].gradheight
3638 lastx = foreX[cntI];
3639 lasty = foreY[cntI];
3642 if (im->gdes[i].gf != GF_GRAD) {
3643 gfx_add_point(im, backX[idxI], backY[idxI]);
3645 gfx_add_rect_fadey(im,
3647 backX[idxI], backY[idxI], lasty,
3650 im->gdes[i].gradheight);
3651 lastx = backX[idxI];
3652 lasty = backY[idxI];
3659 AlmostEqual2sComplement(backY
3663 AlmostEqual2sComplement(backY
3670 if (im->gdes[i].gf != GF_GRAD) {
3671 gfx_add_point(im, backX[idxI], backY[idxI]);
3673 gfx_add_rect_fadey(im,
3675 backX[idxI], backY[idxI], lasty,
3678 im->gdes[i].gradheight);
3679 lastx = backX[idxI];
3680 lasty = backY[idxI];
3685 if (im->gdes[i].gf != GF_GRAD)
3692 if (ii == im->xsize)
3694 if (im->slopemode == 0 && ii == 0) {
3697 if (isnan(im->gdes[i].p_data[ii])) {
3701 ytop = ytr(im, im->gdes[i].p_data[ii]);
3702 if (lastgdes && im->gdes[i].stack) {
3703 ybase = ytr(im, lastgdes->p_data[ii]);
3705 ybase = ytr(im, areazero);
3707 if (ybase == ytop) {
3713 double extra = ytop;
3718 if (im->slopemode == 0) {
3719 backY[++idxI] = ybase - 0.2;
3720 backX[idxI] = ii + im->xorigin - 1;
3721 foreY[idxI] = ytop + 0.2;
3722 foreX[idxI] = ii + im->xorigin - 1;
3724 backY[++idxI] = ybase - 0.2;
3725 backX[idxI] = ii + im->xorigin;
3726 foreY[idxI] = ytop + 0.2;
3727 foreX[idxI] = ii + im->xorigin;
3729 /* close up any remaining area */
3734 } /* else GF_LINE */
3736 /* if color != 0x0 */
3737 /* make sure we do not run into trouble when stacking on NaN */
3738 for (ii = 0; ii < im->xsize; ii++) {
3739 if (isnan(im->gdes[i].p_data[ii])) {
3740 if (lastgdes && (im->gdes[i].stack)) {
3741 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3743 im->gdes[i].p_data[ii] = areazero;
3747 lastgdes = &(im->gdes[i]);
3749 } /* GF_AREA, GF_LINE, GF_GRAD */
3752 ("STACK should already be turned into LINE or AREA here");
3757 cairo_reset_clip(im->cr);
3759 /* grid_paint also does the text */
3760 if (!(im->extra_flags & ONLY_GRAPH))
3762 if (!(im->extra_flags & ONLY_GRAPH))
3764 /* the RULES are the last thing to paint ... */
3765 for (i = 0; i < im->gdes_c; i++) {
3767 switch (im->gdes[i].gf) {
3769 if (im->gdes[i].yrule >= im->minval
3770 && im->gdes[i].yrule <= im->maxval) {
3772 if (im->gdes[i].dash) {
3773 cairo_set_dash(im->cr,
3774 im->gdes[i].p_dashes,
3775 im->gdes[i].ndash, im->gdes[i].offset);
3777 gfx_line(im, im->xorigin,
3778 ytr(im, im->gdes[i].yrule),
3779 im->xorigin + im->xsize,
3780 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3781 cairo_stroke(im->cr);
3782 cairo_restore(im->cr);
3786 if (im->gdes[i].xrule >= im->start
3787 && im->gdes[i].xrule <= im->end) {
3789 if (im->gdes[i].dash) {
3790 cairo_set_dash(im->cr,
3791 im->gdes[i].p_dashes,
3792 im->gdes[i].ndash, im->gdes[i].offset);
3795 xtr(im, im->gdes[i].xrule),
3796 im->yorigin, xtr(im,
3800 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3801 cairo_stroke(im->cr);
3802 cairo_restore(im->cr);
3811 switch (im->imgformat) {
3814 cairo_status_t status;
3816 status = strlen(im->graphfile) ?
3817 cairo_surface_write_to_png(im->surface, im->graphfile)
3818 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3821 if (status != CAIRO_STATUS_SUCCESS) {
3822 rrd_set_error("Could not save png to '%s'", im->graphfile);
3828 if (strlen(im->graphfile)) {
3829 cairo_show_page(im->cr);
3831 cairo_surface_finish(im->surface);
3840 /*****************************************************
3842 *****************************************************/
3849 if ((im->gdes = (graph_desc_t *)
3850 rrd_realloc(im->gdes, (im->gdes_c)
3851 * sizeof(graph_desc_t))) == NULL) {
3852 rrd_set_error("realloc graph_descs");
3857 im->gdes[im->gdes_c - 1].step = im->step;
3858 im->gdes[im->gdes_c - 1].step_orig = im->step;
3859 im->gdes[im->gdes_c - 1].stack = 0;
3860 im->gdes[im->gdes_c - 1].linewidth = 0;
3861 im->gdes[im->gdes_c - 1].debug = 0;
3862 im->gdes[im->gdes_c - 1].start = im->start;
3863 im->gdes[im->gdes_c - 1].start_orig = im->start;
3864 im->gdes[im->gdes_c - 1].end = im->end;
3865 im->gdes[im->gdes_c - 1].end_orig = im->end;
3866 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3867 im->gdes[im->gdes_c - 1].data = NULL;
3868 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3869 im->gdes[im->gdes_c - 1].data_first = 0;
3870 im->gdes[im->gdes_c - 1].p_data = NULL;
3871 im->gdes[im->gdes_c - 1].rpnp = NULL;
3872 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3873 im->gdes[im->gdes_c - 1].shift = 0.0;
3874 im->gdes[im->gdes_c - 1].dash = 0;
3875 im->gdes[im->gdes_c - 1].ndash = 0;
3876 im->gdes[im->gdes_c - 1].offset = 0;
3877 im->gdes[im->gdes_c - 1].col.red = 0.0;
3878 im->gdes[im->gdes_c - 1].col.green = 0.0;
3879 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3880 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3881 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3882 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3883 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3884 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3885 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3886 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3887 im->gdes[im->gdes_c - 1].format[0] = '\0';
3888 im->gdes[im->gdes_c - 1].strftm = 0;
3889 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3890 im->gdes[im->gdes_c - 1].ds = -1;
3891 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3892 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3893 im->gdes[im->gdes_c - 1].yrule = DNAN;
3894 im->gdes[im->gdes_c - 1].xrule = 0;
3895 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3899 /* copies input untill the first unescaped colon is found
3900 or until input ends. backslashes have to be escaped as well */
3902 const char *const input,
3908 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3909 if (input[inp] == '\\'
3910 && input[inp + 1] != '\0'
3911 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3912 output[outp++] = input[++inp];
3914 output[outp++] = input[inp];
3917 output[outp] = '\0';
3921 /* Now just a wrapper around rrd_graph_v */
3933 rrd_info_t *grinfo = NULL;
3936 grinfo = rrd_graph_v(argc, argv);
3942 if (strcmp(walker->key, "image_info") == 0) {
3945 (char**)rrd_realloc((*prdata),
3946 (prlines + 1) * sizeof(char *))) == NULL) {
3947 rrd_set_error("realloc prdata");
3950 /* imginfo goes to position 0 in the prdata array */
3951 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3952 + 2) * sizeof(char));
3953 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3954 (*prdata)[prlines] = NULL;
3956 /* skip anything else */
3957 walker = walker->next;
3965 if (strcmp(walker->key, "image_width") == 0) {
3966 *xsize = walker->value.u_cnt;
3967 } else if (strcmp(walker->key, "image_height") == 0) {
3968 *ysize = walker->value.u_cnt;
3969 } else if (strcmp(walker->key, "value_min") == 0) {
3970 *ymin = walker->value.u_val;
3971 } else if (strcmp(walker->key, "value_max") == 0) {
3972 *ymax = walker->value.u_val;
3973 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3976 (char**)rrd_realloc((*prdata),
3977 (prlines + 1) * sizeof(char *))) == NULL) {
3978 rrd_set_error("realloc prdata");
3981 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3982 + 2) * sizeof(char));
3983 (*prdata)[prlines] = NULL;
3984 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3985 } else if (strcmp(walker->key, "image") == 0) {
3986 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3987 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3988 rrd_set_error("writing image");
3992 /* skip anything else */
3993 walker = walker->next;
3995 rrd_info_free(grinfo);
4000 /* Some surgery done on this function, it became ridiculously big.
4002 ** - initializing now in rrd_graph_init()
4003 ** - options parsing now in rrd_graph_options()
4004 ** - script parsing now in rrd_graph_script()
4006 rrd_info_t *rrd_graph_v(
4013 rrd_graph_init(&im);
4014 /* a dummy surface so that we can measure text sizes for placements */
4015 old_locale = setlocale(LC_NUMERIC, NULL);
4016 setlocale(LC_NUMERIC, "C");
4017 rrd_graph_options(argc, argv, &im);
4018 if (rrd_test_error()) {
4019 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4020 rrd_info_free(im.grinfo);
4025 if (optind >= argc) {
4026 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4027 rrd_info_free(im.grinfo);
4029 rrd_set_error("missing filename");
4033 if (strlen(argv[optind]) >= MAXPATH) {
4034 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4035 rrd_set_error("filename (including path) too long");
4036 rrd_info_free(im.grinfo);
4041 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4042 im.graphfile[MAXPATH - 1] = '\0';
4044 if (strcmp(im.graphfile, "-") == 0) {
4045 im.graphfile[0] = '\0';
4048 rrd_graph_script(argc, argv, &im, 1);
4049 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4051 if (rrd_test_error()) {
4052 rrd_info_free(im.grinfo);
4057 /* Everything is now read and the actual work can start */
4059 if (graph_paint(&im) == -1) {
4060 rrd_info_free(im.grinfo);
4066 /* The image is generated and needs to be output.
4067 ** Also, if needed, print a line with information about the image.
4075 path = strdup(im.graphfile);
4076 filename = basename(path);
4078 sprintf_alloc(im.imginfo,
4081 im.ximg), (long) (im.zoom * im.yimg));
4082 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4086 if (im.rendered_image) {
4089 img.u_blo.size = im.rendered_image_size;
4090 img.u_blo.ptr = im.rendered_image;
4091 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4100 image_desc_t *im,int prop,char *font, double size ){
4102 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4103 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4104 /* if we already got one, drop it first */
4105 pango_font_description_free(im->text_prop[prop].font_desc);
4106 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4109 im->text_prop[prop].size = size;
4111 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4112 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4116 void rrd_graph_init(
4121 char *deffont = getenv("RRD_DEFAULT_FONT");
4122 static PangoFontMap *fontmap = NULL;
4123 PangoContext *context;
4130 im->daemon_addr = NULL;
4131 im->draw_x_grid = 1;
4132 im->draw_y_grid = 1;
4133 im->draw_3d_border = 2;
4134 im->dynamic_labels = 0;
4135 im->extra_flags = 0;
4136 im->font_options = cairo_font_options_create();
4137 im->forceleftspace = 0;
4140 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4141 im->grid_dash_off = 1;
4142 im->grid_dash_on = 1;
4144 im->grinfo = (rrd_info_t *) NULL;
4145 im->grinfo_current = (rrd_info_t *) NULL;
4146 im->imgformat = IF_PNG;
4149 im->legenddirection = TOP_DOWN;
4150 im->legendheight = 0;
4151 im->legendposition = SOUTH;
4152 im->legendwidth = 0;
4153 im->logarithmic = 0;
4159 im->rendered_image_size = 0;
4160 im->rendered_image = NULL;
4164 im->tabwidth = 40.0;
4165 im->title[0] = '\0';
4166 im->unitsexponent = 9999;
4167 im->unitslength = 6;
4168 im->viewfactor = 1.0;
4169 im->watermark[0] = '\0';
4170 im->with_markup = 0;
4172 im->xlab_user.minsec = -1;
4174 im->xOriginLegend = 0;
4175 im->xOriginLegendY = 0;
4176 im->xOriginLegendY2 = 0;
4177 im->xOriginTitle = 0;
4179 im->ygridstep = DNAN;
4181 im->ylegend[0] = '\0';
4182 im->second_axis_scale = 0; /* 0 disables it */
4183 im->second_axis_shift = 0; /* no shift by default */
4184 im->second_axis_legend[0] = '\0';
4185 im->second_axis_format[0] = '\0';
4187 im->yOriginLegend = 0;
4188 im->yOriginLegendY = 0;
4189 im->yOriginLegendY2 = 0;
4190 im->yOriginTitle = 0;
4194 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4195 im->cr = cairo_create(im->surface);
4197 for (i = 0; i < DIM(text_prop); i++) {
4198 im->text_prop[i].size = -1;
4199 im->text_prop[i].font_desc = NULL;
4200 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4203 if (fontmap == NULL){
4204 fontmap = pango_cairo_font_map_get_default();
4207 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4209 pango_cairo_context_set_resolution(context, 100);
4211 pango_cairo_update_context(im->cr,context);
4213 im->layout = pango_layout_new(context);
4214 g_object_unref (context);
4216 // im->layout = pango_cairo_create_layout(im->cr);
4219 cairo_font_options_set_hint_style
4220 (im->font_options, CAIRO_HINT_STYLE_FULL);
4221 cairo_font_options_set_hint_metrics
4222 (im->font_options, CAIRO_HINT_METRICS_ON);
4223 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4227 for (i = 0; i < DIM(graph_col); i++)
4228 im->graph_col[i] = graph_col[i];
4234 void rrd_graph_options(
4241 char *parsetime_error = NULL;
4242 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4243 time_t start_tmp = 0, end_tmp = 0;
4245 rrd_time_value_t start_tv, end_tv;
4246 long unsigned int color;
4248 /* defines for long options without a short equivalent. should be bytes,
4249 and may not collide with (the ASCII value of) short options */
4250 #define LONGOPT_UNITS_SI 255
4253 struct option long_options[] = {
4254 { "alt-autoscale", no_argument, 0, 'A'},
4255 { "imgformat", required_argument, 0, 'a'},
4256 { "font-smoothing-threshold", required_argument, 0, 'B'},
4257 { "base", required_argument, 0, 'b'},
4258 { "color", required_argument, 0, 'c'},
4259 { "full-size-mode", no_argument, 0, 'D'},
4260 { "daemon", required_argument, 0, 'd'},
4261 { "slope-mode", no_argument, 0, 'E'},
4262 { "end", required_argument, 0, 'e'},
4263 { "force-rules-legend", no_argument, 0, 'F'},
4264 { "imginfo", required_argument, 0, 'f'},
4265 { "graph-render-mode", required_argument, 0, 'G'},
4266 { "no-legend", no_argument, 0, 'g'},
4267 { "height", required_argument, 0, 'h'},
4268 { "no-minor", no_argument, 0, 'I'},
4269 { "interlaced", no_argument, 0, 'i'},
4270 { "alt-autoscale-min", no_argument, 0, 'J'},
4271 { "only-graph", no_argument, 0, 'j'},
4272 { "units-length", required_argument, 0, 'L'},
4273 { "lower-limit", required_argument, 0, 'l'},
4274 { "alt-autoscale-max", no_argument, 0, 'M'},
4275 { "zoom", required_argument, 0, 'm'},
4276 { "no-gridfit", no_argument, 0, 'N'},
4277 { "font", required_argument, 0, 'n'},
4278 { "logarithmic", no_argument, 0, 'o'},
4279 { "pango-markup", no_argument, 0, 'P'},
4280 { "font-render-mode", required_argument, 0, 'R'},
4281 { "rigid", no_argument, 0, 'r'},
4282 { "step", required_argument, 0, 'S'},
4283 { "start", required_argument, 0, 's'},
4284 { "tabwidth", required_argument, 0, 'T'},
4285 { "title", required_argument, 0, 't'},
4286 { "upper-limit", required_argument, 0, 'u'},
4287 { "vertical-label", required_argument, 0, 'v'},
4288 { "watermark", required_argument, 0, 'W'},
4289 { "width", required_argument, 0, 'w'},
4290 { "units-exponent", required_argument, 0, 'X'},
4291 { "x-grid", required_argument, 0, 'x'},
4292 { "alt-y-grid", no_argument, 0, 'Y'},
4293 { "y-grid", required_argument, 0, 'y'},
4294 { "lazy", no_argument, 0, 'z'},
4295 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4296 { "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 */
4297 { "disable-rrdtool-tag",no_argument, 0, 1001},
4298 { "right-axis", required_argument, 0, 1002},
4299 { "right-axis-label", required_argument, 0, 1003},
4300 { "right-axis-format", required_argument, 0, 1004},
4301 { "legend-position", required_argument, 0, 1005},
4302 { "legend-direction", required_argument, 0, 1006},
4303 { "border", required_argument, 0, 1007},
4304 { "grid-dash", required_argument, 0, 1008},
4305 { "dynamic-labels", no_argument, 0, 1009},
4306 { "week-fmt", required_argument, 0, 1010},
4312 opterr = 0; /* initialize getopt */
4313 rrd_parsetime("end-24h", &start_tv);
4314 rrd_parsetime("now", &end_tv);
4316 int option_index = 0;
4318 int col_start, col_end;
4320 opt = getopt_long(argc, argv,
4321 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Mm:Nn:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4322 long_options, &option_index);
4327 im->extra_flags |= NOMINOR;
4330 im->extra_flags |= ALTYGRID;
4333 im->extra_flags |= ALTAUTOSCALE;
4336 im->extra_flags |= ALTAUTOSCALE_MIN;
4339 im->extra_flags |= ALTAUTOSCALE_MAX;
4342 im->extra_flags |= ONLY_GRAPH;
4345 im->extra_flags |= NOLEGEND;
4348 if (strcmp(optarg, "north") == 0) {
4349 im->legendposition = NORTH;
4350 } else if (strcmp(optarg, "west") == 0) {
4351 im->legendposition = WEST;
4352 } else if (strcmp(optarg, "south") == 0) {
4353 im->legendposition = SOUTH;
4354 } else if (strcmp(optarg, "east") == 0) {
4355 im->legendposition = EAST;
4357 rrd_set_error("unknown legend-position '%s'", optarg);
4362 if (strcmp(optarg, "topdown") == 0) {
4363 im->legenddirection = TOP_DOWN;
4364 } else if (strcmp(optarg, "bottomup") == 0) {
4365 im->legenddirection = BOTTOM_UP;
4367 rrd_set_error("unknown legend-position '%s'", optarg);
4372 im->extra_flags |= FORCE_RULES_LEGEND;
4375 im->extra_flags |= NO_RRDTOOL_TAG;
4377 case LONGOPT_UNITS_SI:
4378 if (im->extra_flags & FORCE_UNITS) {
4379 rrd_set_error("--units can only be used once!");
4382 if (strcmp(optarg, "si") == 0)
4383 im->extra_flags |= FORCE_UNITS_SI;
4385 rrd_set_error("invalid argument for --units: %s", optarg);
4390 im->unitsexponent = atoi(optarg);
4393 im->unitslength = atoi(optarg);
4394 im->forceleftspace = 1;
4397 im->tabwidth = atof(optarg);
4400 im->step = atoi(optarg);
4406 im->with_markup = 1;
4409 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4410 rrd_set_error("start time: %s", parsetime_error);
4415 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4416 rrd_set_error("end time: %s", parsetime_error);
4421 if (strcmp(optarg, "none") == 0) {
4422 im->draw_x_grid = 0;
4426 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4428 &im->xlab_user.gridst,
4430 &im->xlab_user.mgridst,
4432 &im->xlab_user.labst,
4433 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4434 strncpy(im->xlab_form, optarg + stroff,
4435 sizeof(im->xlab_form) - 1);
4436 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4438 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4439 rrd_set_error("unknown keyword %s", scan_gtm);
4442 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4444 rrd_set_error("unknown keyword %s", scan_mtm);
4447 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4448 rrd_set_error("unknown keyword %s", scan_ltm);
4451 im->xlab_user.minsec = 1;
4452 im->xlab_user.stst = im->xlab_form;
4454 rrd_set_error("invalid x-grid format");
4460 if (strcmp(optarg, "none") == 0) {
4461 im->draw_y_grid = 0;
4464 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4465 if (im->ygridstep <= 0) {
4466 rrd_set_error("grid step must be > 0");
4468 } else if (im->ylabfact < 1) {
4469 rrd_set_error("label factor must be > 0");
4473 rrd_set_error("invalid y-grid format");
4478 im->draw_3d_border = atoi(optarg);
4480 case 1008: /* grid-dash */
4484 &im->grid_dash_off) != 2) {
4485 rrd_set_error("expected grid-dash format float:float");
4489 case 1009: /* enable dynamic labels */
4490 im->dynamic_labels = 1;
4493 strncpy(week_fmt,optarg,sizeof week_fmt);
4494 week_fmt[(sizeof week_fmt)-1]='\0';
4496 case 1002: /* right y axis */
4500 &im->second_axis_scale,
4501 &im->second_axis_shift) == 2) {
4502 if(im->second_axis_scale==0){
4503 rrd_set_error("the second_axis_scale must not be 0");
4507 rrd_set_error("invalid right-axis format expected scale:shift");
4512 strncpy(im->second_axis_legend,optarg,150);
4513 im->second_axis_legend[150]='\0';
4516 if (bad_format(optarg)){
4517 rrd_set_error("use either %le or %lf formats");
4520 strncpy(im->second_axis_format,optarg,150);
4521 im->second_axis_format[150]='\0';
4524 strncpy(im->ylegend, optarg, 150);
4525 im->ylegend[150] = '\0';
4528 im->maxval = atof(optarg);
4531 im->minval = atof(optarg);
4534 im->base = atol(optarg);
4535 if (im->base != 1024 && im->base != 1000) {
4537 ("the only sensible value for base apart from 1000 is 1024");
4542 long_tmp = atol(optarg);
4543 if (long_tmp < 10) {
4544 rrd_set_error("width below 10 pixels");
4547 im->xsize = long_tmp;
4550 long_tmp = atol(optarg);
4551 if (long_tmp < 10) {
4552 rrd_set_error("height below 10 pixels");
4555 im->ysize = long_tmp;
4558 im->extra_flags |= FULL_SIZE_MODE;
4561 /* interlaced png not supported at the moment */
4567 im->imginfo = optarg;
4571 (im->imgformat = if_conv(optarg)) == -1) {
4572 rrd_set_error("unsupported graphics format '%s'", optarg);
4583 im->logarithmic = 1;
4587 "%10[A-Z]#%n%8lx%n",
4588 col_nam, &col_start, &color, &col_end) == 2) {
4590 int col_len = col_end - col_start;
4595 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4603 (((color & 0xF000) *
4604 0x11000) | ((color & 0x0F00) *
4605 0x01100) | ((color &
4608 ((color & 0x000F) * 0x00011)
4612 color = (color << 8) + 0xff /* shift left by 8 */ ;
4617 rrd_set_error("the color format is #RRGGBB[AA]");
4620 if ((ci = grc_conv(col_nam)) != -1) {
4621 im->graph_col[ci] = gfx_hex_to_col(color);
4623 rrd_set_error("invalid color name '%s'", col_nam);
4627 rrd_set_error("invalid color def format");
4636 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4637 int sindex, propidx;
4639 if ((sindex = text_prop_conv(prop)) != -1) {
4640 for (propidx = sindex;
4641 propidx < TEXT_PROP_LAST; propidx++) {
4643 rrd_set_font_desc(im,propidx,NULL,size);
4645 if ((int) strlen(optarg) > end+2) {
4646 if (optarg[end] == ':') {
4647 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4650 ("expected : after font size in '%s'",
4655 /* only run the for loop for DEFAULT (0) for
4656 all others, we break here. woodo programming */
4657 if (propidx == sindex && sindex != 0)
4661 rrd_set_error("invalid fonttag '%s'", prop);
4665 rrd_set_error("invalid text property format");
4671 im->zoom = atof(optarg);
4672 if (im->zoom <= 0.0) {
4673 rrd_set_error("zoom factor must be > 0");
4678 strncpy(im->title, optarg, 150);
4679 im->title[150] = '\0';
4682 if (strcmp(optarg, "normal") == 0) {
4683 cairo_font_options_set_antialias
4684 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4685 cairo_font_options_set_hint_style
4686 (im->font_options, CAIRO_HINT_STYLE_FULL);
4687 } else if (strcmp(optarg, "light") == 0) {
4688 cairo_font_options_set_antialias
4689 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4690 cairo_font_options_set_hint_style
4691 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4692 } else if (strcmp(optarg, "mono") == 0) {
4693 cairo_font_options_set_antialias
4694 (im->font_options, CAIRO_ANTIALIAS_NONE);
4695 cairo_font_options_set_hint_style
4696 (im->font_options, CAIRO_HINT_STYLE_FULL);
4698 rrd_set_error("unknown font-render-mode '%s'", optarg);
4703 if (strcmp(optarg, "normal") == 0)
4704 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4705 else if (strcmp(optarg, "mono") == 0)
4706 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4708 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4713 /* not supported curently */
4716 strncpy(im->watermark, optarg, 100);
4717 im->watermark[99] = '\0';
4721 if (im->daemon_addr != NULL)
4723 rrd_set_error ("You cannot specify --daemon "
4728 im->daemon_addr = strdup(optarg);
4729 if (im->daemon_addr == NULL)
4731 rrd_set_error("strdup failed");
4739 rrd_set_error("unknown option '%c'", optopt);
4741 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4746 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4747 pango_layout_context_changed(im->layout);
4751 if (im->logarithmic && im->minval <= 0) {
4753 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4757 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4758 /* error string is set in rrd_parsetime.c */
4762 if (start_tmp < 3600 * 24 * 365 * 10) {
4764 ("the first entry to fetch should be after 1980 (%ld)",
4769 if (end_tmp < start_tmp) {
4771 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4775 im->start = start_tmp;
4777 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4780 int rrd_graph_color(
4788 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4790 color = strstr(var, "#");
4791 if (color == NULL) {
4792 if (optional == 0) {
4793 rrd_set_error("Found no color in %s", err);
4800 long unsigned int col;
4802 rest = strstr(color, ":");
4809 sscanf(color, "#%6lx%n", &col, &n);
4810 col = (col << 8) + 0xff /* shift left by 8 */ ;
4812 rrd_set_error("Color problem in %s", err);
4815 sscanf(color, "#%8lx%n", &col, &n);
4819 rrd_set_error("Color problem in %s", err);
4821 if (rrd_test_error())
4823 gdp->col = gfx_hex_to_col(col);
4836 while (*ptr != '\0')
4837 if (*ptr++ == '%') {
4839 /* line cannot end with percent char */
4842 /* '%s', '%S' and '%%' are allowed */
4843 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4845 /* %c is allowed (but use only with vdef!) */
4846 else if (*ptr == 'c') {
4851 /* or else '% 6.2lf' and such are allowed */
4853 /* optional padding character */
4854 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4856 /* This should take care of 'm.n' with all three optional */
4857 while (*ptr >= '0' && *ptr <= '9')
4861 while (*ptr >= '0' && *ptr <= '9')
4863 /* Either 'le', 'lf' or 'lg' must follow here */
4866 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4881 const char *const str)
4883 /* A VDEF currently is either "func" or "param,func"
4884 * so the parsing is rather simple. Change if needed.
4891 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4892 if (n == (int) strlen(str)) { /* matched */
4896 sscanf(str, "%29[A-Z]%n", func, &n);
4897 if (n == (int) strlen(str)) { /* matched */
4901 ("Unknown function string '%s' in VDEF '%s'",
4906 if (!strcmp("PERCENT", func))
4907 gdes->vf.op = VDEF_PERCENT;
4908 else if (!strcmp("PERCENTNAN", func))
4909 gdes->vf.op = VDEF_PERCENTNAN;
4910 else if (!strcmp("MAXIMUM", func))
4911 gdes->vf.op = VDEF_MAXIMUM;
4912 else if (!strcmp("AVERAGE", func))
4913 gdes->vf.op = VDEF_AVERAGE;
4914 else if (!strcmp("STDEV", func))
4915 gdes->vf.op = VDEF_STDEV;
4916 else if (!strcmp("MINIMUM", func))
4917 gdes->vf.op = VDEF_MINIMUM;
4918 else if (!strcmp("TOTAL", func))
4919 gdes->vf.op = VDEF_TOTAL;
4920 else if (!strcmp("FIRST", func))
4921 gdes->vf.op = VDEF_FIRST;
4922 else if (!strcmp("LAST", func))
4923 gdes->vf.op = VDEF_LAST;
4924 else if (!strcmp("LSLSLOPE", func))
4925 gdes->vf.op = VDEF_LSLSLOPE;
4926 else if (!strcmp("LSLINT", func))
4927 gdes->vf.op = VDEF_LSLINT;
4928 else if (!strcmp("LSLCORREL", func))
4929 gdes->vf.op = VDEF_LSLCORREL;
4932 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4935 switch (gdes->vf.op) {
4937 case VDEF_PERCENTNAN:
4938 if (isnan(param)) { /* no parameter given */
4940 ("Function '%s' needs parameter in VDEF '%s'\n",
4944 if (param >= 0.0 && param <= 100.0) {
4945 gdes->vf.param = param;
4946 gdes->vf.val = DNAN; /* undefined */
4947 gdes->vf.when = 0; /* undefined */
4951 ("Parameter '%f' out of range in VDEF '%s'\n",
4952 param, gdes->vname);
4965 case VDEF_LSLCORREL:
4967 gdes->vf.param = DNAN;
4968 gdes->vf.val = DNAN;
4973 ("Function '%s' needs no parameter in VDEF '%s'\n",
4987 graph_desc_t *src, *dst;
4991 dst = &im->gdes[gdi];
4992 src = &im->gdes[dst->vidx];
4993 data = src->data + src->ds;
4995 steps = (src->end - src->start) / src->step;
4998 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4999 src->start, src->end, steps);
5001 switch (dst->vf.op) {
5005 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5006 rrd_set_error("malloc VDEV_PERCENT");
5009 for (step = 0; step < steps; step++) {
5010 array[step] = data[step * src->ds_cnt];
5012 qsort(array, step, sizeof(double), vdef_percent_compar);
5013 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5014 dst->vf.val = array[field];
5015 dst->vf.when = 0; /* no time component */
5019 for (step = 0; step < steps; step++)
5020 printf("DEBUG: %3li:%10.2f %c\n",
5021 step, array[step], step == field ? '*' : ' ');
5025 case VDEF_PERCENTNAN:{
5028 /* count number of "valid" values */
5030 for (step = 0; step < steps; step++) {
5031 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5033 /* and allocate it */
5034 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5035 rrd_set_error("malloc VDEV_PERCENT");
5038 /* and fill it in */
5040 for (step = 0; step < steps; step++) {
5041 if (!isnan(data[step * src->ds_cnt])) {
5042 array[field] = data[step * src->ds_cnt];
5046 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5047 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5048 dst->vf.val = array[field];
5049 dst->vf.when = 0; /* no time component */
5056 while (step != steps && isnan(data[step * src->ds_cnt]))
5058 if (step == steps) {
5063 dst->vf.val = data[step * src->ds_cnt];
5064 dst->vf.when = src->start + (step + 1) * src->step;
5067 while (step != steps) {
5068 if (finite(data[step * src->ds_cnt])) {
5069 if (data[step * src->ds_cnt] > dst->vf.val) {
5070 dst->vf.val = data[step * src->ds_cnt];
5071 dst->vf.when = src->start + (step + 1) * src->step;
5083 double average = 0.0;
5085 for (step = 0; step < steps; step++) {
5086 if (finite(data[step * src->ds_cnt])) {
5087 sum += data[step * src->ds_cnt];
5092 if (dst->vf.op == VDEF_TOTAL) {
5093 dst->vf.val = sum * src->step;
5094 dst->vf.when = 0; /* no time component */
5096 } else if (dst->vf.op == VDEF_AVERAGE) {
5097 dst->vf.val = sum / cnt;
5098 dst->vf.when = 0; /* no time component */
5101 average = sum / cnt;
5103 for (step = 0; step < steps; step++) {
5104 if (finite(data[step * src->ds_cnt])) {
5105 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5108 dst->vf.val = pow(sum / cnt, 0.5);
5109 dst->vf.when = 0; /* no time component */
5121 while (step != steps && isnan(data[step * src->ds_cnt]))
5123 if (step == steps) {
5128 dst->vf.val = data[step * src->ds_cnt];
5129 dst->vf.when = src->start + (step + 1) * src->step;
5132 while (step != steps) {
5133 if (finite(data[step * src->ds_cnt])) {
5134 if (data[step * src->ds_cnt] < dst->vf.val) {
5135 dst->vf.val = data[step * src->ds_cnt];
5136 dst->vf.when = src->start + (step + 1) * src->step;
5144 /* The time value returned here is one step before the
5145 * actual time value. This is the start of the first
5149 while (step != steps && isnan(data[step * src->ds_cnt]))
5151 if (step == steps) { /* all entries were NaN */
5156 dst->vf.val = data[step * src->ds_cnt];
5157 dst->vf.when = src->start + step * src->step;
5162 /* The time value returned here is the
5163 * actual time value. This is the end of the last
5167 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5169 if (step < 0) { /* all entries were NaN */
5174 dst->vf.val = data[step * src->ds_cnt];
5175 dst->vf.when = src->start + (step + 1) * src->step;
5181 case VDEF_LSLCORREL:{
5182 /* Bestfit line by linear least squares method */
5185 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5192 for (step = 0; step < steps; step++) {
5193 if (finite(data[step * src->ds_cnt])) {
5196 SUMxx += step * step;
5197 SUMxy += step * data[step * src->ds_cnt];
5198 SUMy += data[step * src->ds_cnt];
5199 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5203 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5204 y_intercept = (SUMy - slope * SUMx) / cnt;
5207 (SUMx * SUMy) / cnt) /
5209 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5211 if (dst->vf.op == VDEF_LSLSLOPE) {
5212 dst->vf.val = slope;
5215 } else if (dst->vf.op == VDEF_LSLINT) {
5216 dst->vf.val = y_intercept;
5219 } else if (dst->vf.op == VDEF_LSLCORREL) {
5220 dst->vf.val = correl;
5235 /* NaN < -INF < finite_values < INF */
5236 int vdef_percent_compar(
5242 /* Equality is not returned; this doesn't hurt except
5243 * (maybe) for a little performance.
5246 /* First catch NaN values. They are smallest */
5247 if (isnan(*(double *) a))
5249 if (isnan(*(double *) b))
5251 /* NaN doesn't reach this part so INF and -INF are extremes.
5252 * The sign from isinf() is compatible with the sign we return
5254 if (isinf(*(double *) a))
5255 return isinf(*(double *) a);
5256 if (isinf(*(double *) b))
5257 return isinf(*(double *) b);
5258 /* If we reach this, both values must be finite */
5259 if (*(double *) a < *(double *) b)
5268 rrd_info_type_t type,
5269 rrd_infoval_t value)
5271 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5272 if (im->grinfo == NULL) {
5273 im->grinfo = im->grinfo_current;
5284 /* Handling based on
5285 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5286 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5287 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5288 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5289 Specifications tells
5290 "If a conversion specifier is not one of the above, the behavior is undefined."
5293 "A conversion specifier consists of a % character, possibly followed by an E or O modifier character (described below), followed by a character that determines the behavior of the conversion specifier.
5296 "A conversion specification consists of a '%' character, possibly followed by an E or O modifier, and a terminating conversion specifier character that determines the conversion specification's behavior."
5298 POSIX:2008 introduce more complexe behavior that are not handled here.
5300 According to this, this code will replace:
5301 - % followed by @ by a %@
5302 - % followed by by a %SPACE
5303 - % followed by . by a %.
5304 - % followed by % by a %
5305 - % followed by t by a TAB
5306 - % followed by E then anything by '-'
5307 - % followed by O then anything by '-'
5308 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5312 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5313 if (format[j] == '%') {
5314 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5316 j+=2; /* We skip next 2 following char */
5317 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5318 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5319 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5320 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5321 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5322 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5324 if (jj < FMT_LEG_LEN) {
5327 j++; /* We skip the following char */
5328 } else if (format[j+1] == 'j') {
5330 if (jj < FMT_LEG_LEN - 1) {
5334 j++; /* We skip the following char */
5335 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5336 /* Assuming Year on 4 digit */
5338 if (jj < FMT_LEG_LEN - 2) {
5343 j++; /* We skip the following char */
5344 } else if (format[j+1] == 'R') {
5346 if (jj < FMT_LEG_LEN - 3) {
5352 j++; /* We skip the following char */
5353 } else if (format[j+1] == 'T') {
5355 if (jj < FMT_LEG_LEN - 6) {
5364 j++; /* We skip the following char */
5365 } else if (format[j+1] == 'F') {
5367 if (jj < FMT_LEG_LEN - 8) {
5378 j++; /* We skip the following char */
5379 } else if (format[j+1] == 'D') {
5381 if (jj < FMT_LEG_LEN - 6) {
5390 j++; /* We skip the following char */
5391 } else if (format[j+1] == 'n') {
5392 result[jj++] = '\r';
5393 result[jj++] = '\n';
5394 j++; /* We skip the following char */
5395 } else if (format[j+1] == 't') {
5396 result[jj++] = '\t';
5397 j++; /* We skip the following char */
5398 } else if (format[j+1] == '%') {
5400 j++; /* We skip the following char */
5401 } else if (format[j+1] == ' ') {
5402 if (jj < FMT_LEG_LEN - 1) {
5406 j++; /* We skip the following char */
5407 } else if (format[j+1] == '.') {
5408 if (jj < FMT_LEG_LEN - 1) {
5412 j++; /* We skip the following char */
5413 } else if (format[j+1] == '@') {
5414 if (jj < FMT_LEG_LEN - 1) {
5418 j++; /* We skip the following char */
5421 j++; /* We skip the following char */
5424 result[jj++] = format[j];
5427 result[jj] = '\0'; /* We must force the end of the string */