Steve Rader <rader with teak.wiscnet.net> (rrd_cgi debugging and LAST)
Terminator rAT <karl_schilke with eli.net>
Tobias Weingartner <weingart with cs.ualberta.ca>
+Thomas Gutzler <thomas.gutzler with gmail.com> dashed lines
Tom Crawley <Tom.Crawley with hi.riotinto.com.au> (GCC&HP configuration)
Travis Brown <tebrown with csh.rit.edu>
Tuc <ttsg with ttsg.com>
* --full-size-mode to specify the outer border of the image and not just of the graphing canvas (Matthew Chambers)
* TEXTALIGN command to alter default text alignment behaviour
* C API supports in-memory graphing with rrd_graph_in_memory (Evan Miller)
+* draw dashed lines in graphs (Thomas Gutzler)
Forecasting (Evan Miller)
-----------
=back
+=head2 Drawing dashed lines
+
+Also works for HRULE and VRULE
+
+=over
+
+=item *
+
+default style: - - - - -
+ LINE1:data#FF0000:"dashed line":DASHED
+
+=item *
+
+more fancy style with offset: - - --- - --- -
+ LINE1:data#FF0000:"another dashed line":DASHED:dashes=15,5,5,10:dash-offset=10
+
=head2 Time ranges
Last four weeks: --start end-4w --end 00:00
B<COMMENT>B<:>I<text>
-B<VRULE>B<:>I<time>B<#>I<color>[B<:>I<legend>]
+B<VRULE>B<:>I<time>B<#>I<color>[B<:>I<legend>][B<:DASHED>[B<:dashes=>I<n_on>[,I<n_off>[,I<n_on>,I<n_off>,[...]]]][B<:dash-offset=>I<offset>]]
-B<HRULE>B<:>I<value>B<#>I<color>[B<:>I<legend>]
+B<HRULE>B<:>I<value>B<#>I<color>[B<:>I<legend>][B<:DASHED>[B<:dashes=>I<n_on>[,I<n_off>[,I<n_on>,I<n_off>,[...]]]][B<:dash-offset=>I<offset>]]
-B<LINE>[I<width>]B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]]
+B<LINE>[I<width>]B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]][B<:DASHED>[B<:dashes=>I<n_on>[,I<n_off>[,I<n_on>,I<n_off>,[...]]]][B<:dash-offset=>I<offset>]]
B<AREA>B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]]
RRDtool 1.2 you have to escape colons in COMMENT text in the same way you
have to escape them in B<*PRINT> commands by writing B<'\:'>.
-=item B<VRULE>B<:>I<time>B<#>I<color> [B<:>I<legend> ]
+=item B<VRULE>B<:>I<time>B<#>I<color>[B<:>I<legend>][B<:DASHED>[B<:dashes=>I<n_on>[,I<n_off>[,I<n_on>,I<n_off>,[...]]]][B<:dash-offset=>I<offset>]]
Draw a vertical line at I<time>. Its color is composed from three
hexadecimal numbers specifying the rgb color components (00 is off, FF is
maximum) red, green and blue followed by an optional alpha. Optionally, a legend box and string is
printed in the legend section. I<time> may be a number or a variable
from a B<VDEF>. It is an error to use I<vname>s from B<DEF> or B<CDEF> here.
+Dashed lines can be drawn using the B<DASHED> modifier. See B<LINE> for more
+details.
-=item B<HRULE>B<:>I<value>B<#>I<color> [ :I<legend> ]
+=item B<HRULE>B<:>I<value>B<#>I<color>[B<:>I<legend>][B<:DASHED>[B<:dashes=>I<n_on>[,I<n_off>[,I<n_on>,I<n_off>,[...]]]][B<:dash-offset=>I<offset>]]
Draw a horizontal line at I<value>. HRULE acts much like LINE except that
will have no effect on the scale of the graph. If a HRULE is outside the
graphing area it will just not be visible.
-=item B<LINE>[I<width>]B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]]
+=item B<LINE>[I<width>]B<:>I<value>[B<#>I<color>][B<:>[I<legend>][B<:STACK>]][B<:DASHED>[B<:dashes=>I<n_on>[,I<n_off>[,I<n_on>,I<n_off>,[...]]]][B<:dash-offset=>I<offset>]]
Draw a line of the specified width onto the graph. I<width> can be a
floating point number. If the color is not specified, the drawing is done
is stacked on top of the previous element which can be a B<LINE> or an
B<AREA>.
+The B<DASHED> modifier enables dashed line style. Without any further options
+a symmetric dashed line with a segment length of 5 pixels will be drawn.
+The dash pattern can be changed using the B<dashes> parameter, which can be
+followed by either one value or an even number (1, 2, 4, 6, ...) of positive
+values. Each value provides the length of alternate I<n_on> and I<n_off>
+portions of the stroke. The B<dash-offset> parameter specifies an I<offset>
+into the pattern at which the stroke begins.
+
When you do not specify a color, you cannot specify a legend. Should
you want to use STACK, use the "LINEx:<value>::STACK" form.
free(im->gdes[i].ds_namv);
}
}
+ /* free allocated memory used for dashed lines */
+ if (im->gdes[i].p_dashes != NULL)
+ free(im->gdes[i].p_dashes);
+
free(im->gdes[i].p_data);
free(im->gdes[i].rpnp);
}
im->graph_col[GRC_FRAME].green,
im->graph_col[GRC_FRAME].blue,
im->graph_col[GRC_FRAME].alpha);
+ if (im->gdes[i].dash) {
+ // make box borders in legend dashed if the graph is dashed
+ double dashes[] = { 3.0 };
+ cairo_set_dash(im->cr, dashes, 1, 0.0);
+ }
cairo_stroke(im->cr);
cairo_restore(im->cr);
}
cairo_new_path(im->cr);
cairo_set_line_width(im->cr, im->gdes[i].linewidth);
+
+ if (im->gdes[i].dash) {
+ cairo_set_dash(im->cr, im->gdes[i].p_dashes,
+ im->gdes[i].ndash, im->gdes[i].offset);
+ }
+
for (ii = 1; ii < im->xsize; ii++) {
if (isnan(im->gdes[i].p_data[ii])
|| (im->slopemode == 1
cairo_restore(im->cr);
} else {
int idxI = -1;
- double *foreY = malloc(sizeof(double) * im->xsize * 2);
- double *foreX = malloc(sizeof(double) * im->xsize * 2);
- double *backY = malloc(sizeof(double) * im->xsize * 2);
- double *backX = malloc(sizeof(double) * im->xsize * 2);
+ double *foreY =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
+ double *foreX =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
+ double *backY =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
+ double *backX =
+ (double *) malloc(sizeof(double) * im->xsize * 2);
int drawem = 0;
for (ii = 0; ii <= im->xsize; ii++) {
case GF_HRULE:
if (im->gdes[i].yrule >= im->minval
&& im->gdes[i].yrule <= im->maxval)
- gfx_line(im,
- im->xorigin, ytr(im, im->gdes[i].yrule),
- im->xorigin + im->xsize, ytr(im,
- im->gdes[i].yrule),
- 1.0, im->gdes[i].col);
+ cairo_save(im->cr);
+ if (im->gdes[i].dash) {
+ cairo_set_dash(im->cr, im->gdes[i].p_dashes,
+ im->gdes[i].ndash, im->gdes[i].offset);
+ }
+ gfx_line(im,
+ im->xorigin, ytr(im, im->gdes[i].yrule),
+ im->xorigin + im->xsize, ytr(im,
+ im->gdes[i].yrule),
+ 1.0, im->gdes[i].col);
+ cairo_stroke(im->cr);
+ cairo_restore(im->cr);
break;
case GF_VRULE:
if (im->gdes[i].xrule >= im->start
&& im->gdes[i].xrule <= im->end)
- gfx_line(im,
- xtr(im, im->gdes[i].xrule), im->yorigin,
- xtr(im, im->gdes[i].xrule),
- im->yorigin - im->ysize, 1.0, im->gdes[i].col);
+ cairo_save(im->cr);
+ if (im->gdes[i].dash) {
+ cairo_set_dash(im->cr, im->gdes[i].p_dashes,
+ im->gdes[i].ndash, im->gdes[i].offset);
+ }
+ gfx_line(im,
+ xtr(im, im->gdes[i].xrule), im->yorigin,
+ xtr(im, im->gdes[i].xrule),
+ im->yorigin - im->ysize, 1.0, im->gdes[i].col);
+ cairo_stroke(im->cr);
+ cairo_restore(im->cr);
break;
default:
break;
im->gdes[im->gdes_c - 1].data_first = 0;
im->gdes[im->gdes_c - 1].p_data = NULL;
im->gdes[im->gdes_c - 1].rpnp = NULL;
+ im->gdes[im->gdes_c - 1].p_dashes = NULL;
im->gdes[im->gdes_c - 1].shift = 0.0;
im->gdes[im->gdes_c - 1].col.red = 0.0;
im->gdes[im->gdes_c - 1].col.green = 0.0;
im->gdes[im->gdes_c - 1].ds = -1;
im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
- im->gdes[im->gdes_c - 1].p_data = NULL;
im->gdes[im->gdes_c - 1].yrule = DNAN;
im->gdes[im->gdes_c - 1].xrule = 0;
return 0;
rrd_value_t *data; /* the raw data drawn from the rrd */
rrd_value_t *p_data; /* processed data, xsize elments */
double linewidth; /* linewideth */
+
+ /* dashed line stuff */
+ int dash; /* boolean, draw dashed line? */
+ double *p_dashes; /* pointer do dash array which keeps the lengths of dashes */
+ int ndash; /* number of dash segments */
+ double offset; /* dash offset along the line */
+
enum txa_en txtalign; /* change default alignment strategy for text */
} graph_desc_t;
case GF_LINE:
if (c1 == ':') {
gdp->linewidth = 1;
- dprintf("- - using default width of 1\n");
+ dprintf("- using default width of 1\n");
} else {
i = 0;
sscanf(&line[*eaten], "%lf:%n", &gdp->linewidth, &i);
&line[*eaten], line);
return 1;
} else {
- dprintf("- - scanned width %f\n", gdp->linewidth);
+ dprintf("- scanned width %f\n", gdp->linewidth);
if (isnan(gdp->linewidth)) {
rrd_set_error
("LINE width '%s' is not a number in line '%s'\n",
(*eaten)++; /* after colon */
/* PART, HRULE, VRULE and TICK cannot be stacked. */
- if ((gdp->gf == GF_HRULE)
- || (gdp->gf == GF_VRULE)
- || (gdp->gf == GF_TICK)
- )
- return 0;
+ if ((gdp->gf != GF_HRULE)
+ && (gdp->gf != GF_VRULE)
+ && (gdp->gf != GF_TICK)) {
- dprintf("- parsing '%s'\n", &line[*eaten]);
- if (line[*eaten] != '\0') {
- dprintf("- still more, should be STACK\n");
+ dprintf("- parsing '%s', looking for STACK\n", &line[*eaten]);
j = scan_for_col(&line[*eaten], 5, tmpstr);
- if (line[*eaten + j] != '\0' && line[*eaten + j] != ':') {
- /* not 5 chars */
- rrd_set_error("STACK expected and not found");
- return 1;
- }
if (!strcmp("STACK", tmpstr)) {
dprintf("- found STACK\n");
gdp->stack = 1;
(*eaten) += j;
- } else {
- rrd_set_error("STACK expected and not found");
- return 1;
+ if (line[*eaten] == ':') {
+ (*eaten) += 1;
+ } else if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ } else {
+ dprintf("- found %s instead of just STACK\n", &line[*eaten]);
+ rrd_set_error("STACK expected but %s found", &line[*eaten]);
+ return 1;
+ }
+ } else
+ dprintf("- not STACKing\n");
+ }
+
+ dprintf("- still more, should be DASHED[:...]\n");
+ dprintf("- parsing '%s'\n", &line[*eaten]);
+ if (line[*eaten] != '\0') {
+ // parse dash arguments here. Possible options:
+ // DASHED
+ // DASHED:dashes=n_on,n_off,n_on,n_off
+ // DASHED:dashes=n_on,n_off,n_on,n_off:dash-offset=offset
+ // start with DASHED
+ j = scan_for_col(&line[*eaten], 6, tmpstr);
+ if (!strcmp("DASHED", tmpstr)) {
+ dprintf("- found %s\n", tmpstr);
+ // initialise all required variables we need for dashed lines
+ // using default dash length of 5 pixels
+ gdp->dash = 1;
+ gdp->p_dashes = (double *) malloc(sizeof(double));
+ gdp->p_dashes[0] = 5;
+ gdp->ndash = 1;
+ gdp->offset = 0;
+ (*eaten) += j;
+ if (line[*eaten] == ':')
+ (*eaten) += 1;
+ }
+ if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ }
+ // DASHED:dashes=n_on,n_off,n_on,n_off
+ // allowing 64 characters for definition of dash style
+ j = scan_for_col(&line[*eaten], 64, tmpstr);
+ if (sscanf(tmpstr, "dashes=%s", tmpstr)) {
+ char csv[64];
+ char *pch;
+ float dsh;
+
+ strcpy(csv, tmpstr);
+ int count = 0;
+
+ pch = strtok(tmpstr, ",");
+ while (pch != NULL) {
+ pch = strtok(NULL, ",");
+ count++;
+ }
+ dprintf("- %d dash values found: ", count);
+ gdp->ndash = count;
+ if (gdp->p_dashes != NULL)
+ free(gdp->p_dashes);
+ gdp->p_dashes = (double *) malloc(sizeof(double) * count);
+ pch = strtok(csv, ",");
+ count = 0;
+ while (pch != NULL) {
+ if (sscanf(pch, "%f", &dsh)) {
+ gdp->p_dashes[count] = (double) dsh;
+ dprintf("%.1f ", gdp->p_dashes[count]);
+ count++;
+ }
+ pch = strtok(NULL, ",");
+ }
+ dprintf("\n");
+ (*eaten) += j;
+ if (line[*eaten] == ':')
+ (*eaten) += 1;
+ }
+ if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
+ }
+ // DASHED:dashes=n_on,n_off,n_on,n_off:dash-offset=offset
+ // allowing 16 characters for dash-offset=....
+ // => 4 characters for the offset value
+ j = scan_for_col(&line[*eaten], 16, tmpstr);
+ if (sscanf(tmpstr, "dash-offset=%lf", &gdp->offset)) {
+ dprintf("- found %s\n", tmpstr);
+ gdp->dash = 1;
+ (*eaten) += j;
+ if (line[*eaten] == ':')
+ (*eaten) += 1;
+ }
+ if (line[*eaten] == '\0') {
+ dprintf("- done parsing line\n");
+ return 0;
}
}
if (line[*eaten] == '\0') {