#####################################
-Major Changes between 1.3.x and 1.4.x
+Major Changes between 1.4.x and ...
-------------------------------------
$Id$
+RRD Graph
+---------
+* GRAD element for graph, it acts more or less like an AREA,
+ except you can specify a second color and a height which is used to
+ create a gradient from one color to the next
+ by Rian Shelley
+
+
+
+
+#####################################
+Major Changes between 1.3.x and 1.4.x
+-------------------------------------
+
RRD Caching Daemon (rrdcached)
------------------------------
by Florian Forster and Kevin Brintnall
See B<LINE>, however the area between the x-axis and the line will
be filled.
+=head3 B<GRAD>B<:>I<value>[B<#>I<color1>[B<#>I<color2>B<:>I<height>][B<:>[I<legend>][B<:STACK>]]
+
+Similar to B<AREA>, except the area between the line and the x-axis will contain a gradient from color1 to color2.
+
+The I<height> parameter can create three different behaviors. If I<height> > 0, then the gradient is a fixed height, starting at the line going down. If I<height> < 0, then the gradient starts at fixed height above the x-axis, going down to the x-axis. If I<height> == 0, then the gradient goes from the line to x-axis.
+
+If not present, I<color2> defaults to #00000000 and I<height> defaults to 50.
+
=head3 B<TICK>B<:>I<vname>B<#>I<rrggbb>[I<aa>][B<:>I<fraction>[B<:>I<legend>]]
Plot a tick mark (a vertical line) for each value of I<vname> that is
cairo_line_to(cr, x, y);
}
+/* add a point to a line or to an area */
+void gfx_add_rect_fadey(
+ image_desc_t *im,
+ double x1,double y1,
+ double x2,double y2,
+ double py,
+ gfx_color_t color1,
+ gfx_color_t color2,
+ double height)
+{
+ cairo_t *cr = im->cr;
+
+ cairo_new_path(cr);
+ gfx_area_fit(im, &x1, &y1);
+ gfx_area_fit(im, &x2, &y2);
+ cairo_line_to(cr, x1, y1);
+ cairo_line_to(cr, x1, y2);
+ cairo_line_to(cr, x2, y2);
+ cairo_line_to(cr, x2, y1);
+ cairo_close_path(cr);
+ cairo_pattern_t* p;
+ if (height < 0) {
+ p = cairo_pattern_create_linear(x1,y1,x2,y1+height);
+ } else if (height > 0) {
+ p = cairo_pattern_create_linear(x1,(y2+py)/2+height,x2,(y2+py)/2);
+ } else {
+ p = cairo_pattern_create_linear(x1,y1,x2,(y2+py)/2);
+ }
+ //cairo_pattern_t* p = cairo_pattern_create_linear(x1,py+50,x2,py);
+ cairo_pattern_add_color_stop_rgba(p, 1, color1.red,color1.green,color1.blue,color1.alpha);
+ cairo_pattern_add_color_stop_rgba(p, 0, color2.red,color2.green,color2.blue,color2.alpha);
+ cairo_set_source(cr, p);
+ cairo_pattern_destroy(p);
+ cairo_fill(cr);
+}
+
void gfx_close_path(
image_desc_t *im)
{
conv_if(VRULE, GF_VRULE);
conv_if(LINE, GF_LINE);
conv_if(AREA, GF_AREA);
+ conv_if(GRAD, GF_GRAD);
conv_if(STACK, GF_STACK);
conv_if(TICK, GF_TICK);
conv_if(TEXTALIGN, GF_TEXTALIGN);
/* memory for the processed data */
for (i = 0; i < im->gdes_c; i++) {
- if ((im->gdes[i].gf == GF_LINE) ||
- (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
+ if ((im->gdes[i].gf == GF_LINE)
+ || (im->gdes[i].gf == GF_AREA)
+ || (im->gdes[i].gf == GF_TICK)
+ || (im->gdes[i].gf == GF_GRAD)
+ ) {
if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
* sizeof(rrd_value_t))) == NULL) {
rrd_set_error("malloc data_proc");
switch (im->gdes[ii].gf) {
case GF_LINE:
case GF_AREA:
+ case GF_GRAD:
case GF_TICK:
if (!im->gdes[ii].stack)
paintval = 0.0;
break;
case GF_LINE:
case GF_AREA:
+ case GF_GRAD:
case GF_TICK:
graphelement = 1;
break;
break;
case GF_LINE:
case GF_AREA:
+ case GF_GRAD:
/* fix data points at oo and -oo */
for (ii = 0; ii < im->xsize; ii++) {
if (isinf(im->gdes[i].p_data[ii])) {
cairo_stroke(im->cr);
cairo_restore(im->cr);
} else {
+ double lastx=0;
+ double lasty=0;
int idxI = -1;
double *foreY =
(double *) malloc(sizeof(double) * im->xsize * 2);
[cntI + 1], 4)) {
cntI++;
}
- gfx_new_area(im,
- backX[0], backY[0],
- foreX[0], foreY[0],
- foreX[cntI],
- foreY[cntI], im->gdes[i].col);
- while (cntI < idxI) {
+ if (im->gdes[i].gf != GF_GRAD) {
+ gfx_new_area(im,
+ backX[0], backY[0],
+ foreX[0], foreY[0],
+ foreX[cntI],
+ foreY[cntI], im->gdes[i].col);
+ } else {
+ lastx = foreX[cntI];
+ lasty = foreY[cntI];
+ }
+ while (cntI < idxI) {
lastI = cntI;
cntI++;
while (cntI < idxI
+ 1], 4)) {
cntI++;
}
- gfx_add_point(im, foreX[cntI], foreY[cntI]);
+ if (im->gdes[i].gf != GF_GRAD) {
+ gfx_add_point(im, foreX[cntI], foreY[cntI]);
+ } else {
+ gfx_add_rect_fadey(im,
+ lastx, foreY[0],
+ foreX[cntI], foreY[cntI], lasty,
+ im->gdes[i].col,
+ im->gdes[i].col2,
+ im->gdes[i].gradheight
+ );
+ lastx = foreX[cntI];
+ lasty = foreY[cntI];
+ }
}
- gfx_add_point(im, backX[idxI], backY[idxI]);
+ if (im->gdes[i].gf != GF_GRAD) {
+ gfx_add_point(im, backX[idxI], backY[idxI]);
+ } else {
+ gfx_add_rect_fadey(im,
+ lastx, foreY[0],
+ backX[idxI], backY[idxI], lasty,
+ im->gdes[i].col,
+ im->gdes[i].col2,
+ im->gdes[i].gradheight);
+ lastx = backX[idxI];
+ lasty = backY[idxI];
+ }
while (idxI > 1) {
lastI = idxI;
idxI--;
- 1], 4)) {
idxI--;
}
- gfx_add_point(im, backX[idxI], backY[idxI]);
+ if (im->gdes[i].gf != GF_GRAD) {
+ gfx_add_point(im, backX[idxI], backY[idxI]);
+ } else {
+ gfx_add_rect_fadey(im,
+ lastx, foreY[0],
+ backX[idxI], backY[idxI], lasty,
+ im->gdes[i].col,
+ im->gdes[i].col2,
+ im->gdes[i].gradheight);
+ lastx = backX[idxI];
+ lasty = backY[idxI];
+ }
}
idxI = -1;
drawem = 0;
- gfx_close_path(im);
+ if (im->gdes[i].gf != GF_GRAD)
+ gfx_close_path(im);
}
if (drawem != 0) {
drawem = 0;
im->gdes[im->gdes_c - 1].col.green = 0.0;
im->gdes[im->gdes_c - 1].col.blue = 0.0;
im->gdes[im->gdes_c - 1].col.alpha = 0.0;
+ im->gdes[im->gdes_c - 1].col2.red = 0.0;
+ im->gdes[im->gdes_c - 1].col2.green = 0.0;
+ im->gdes[im->gdes_c - 1].col2.blue = 0.0;
+ im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
+ im->gdes[im->gdes_c - 1].gradheight = 50.0;
im->gdes[im->gdes_c - 1].legend[0] = '\0';
im->gdes[im->gdes_c - 1].format[0] = '\0';
im->gdes[im->gdes_c - 1].strftm = 0;
#define GRIDWIDTH 0.4
enum gf_en { GF_PRINT = 0, GF_GPRINT, GF_COMMENT, GF_HRULE, GF_VRULE, GF_LINE,
- GF_AREA, GF_STACK, GF_TICK, GF_TEXTALIGN,
+ GF_AREA,GF_GRAD, GF_STACK, GF_TICK, GF_TEXTALIGN,
GF_DEF, GF_CDEF, GF_VDEF, GF_SHIFT,
GF_XPORT
};
long ds; /* data source number */
enum cf_en cf; /* consolidation function */
enum cf_en cf_reduce; /* consolidation function for reduce_data() */
- struct gfx_color_t col; /* graph color */
+ struct gfx_color_t col, col2; /* graph color */
+ double gradheight;
char format[FMT_LEG_LEN + 5]; /* format for PRINT AND GPRINT */
char legend[FMT_LEG_LEN + 5]; /* legend */
int strftm; /* should the VDEF legend be formated with strftime */
double x,
double y);
+/* create a rect that has a gradient from color1 to color2 in height pixels
+ * height > 0:
+ * gradient starts at top and goes down a fixed number of pixels (fire style)
+ * height < 0:
+ * gradient starts at bottom and goes up a fixed number of pixels (constant style)
+ * height == 0:
+ * gradient is stretched between two points
+ */
+void gfx_add_rect_fadey(
+ image_desc_t *im,
+ double x1,double y1,
+ double x2,double y2,
+ double py,
+ gfx_color_t color1,
+ gfx_color_t color2,
+ double height);
+
+
+
/* close current path so it ends at the same point as it started */
void gfx_close_path(
image_desc_t *im);
graph_desc_t *const gdp,
image_desc_t *const im)
{
- int i, j, k;
+ int i, j, k, j2;
int colorfound = 0;
char tmpstr[MAX_VNAME_LEN + 10]; /* vname#RRGGBBAA\0 */
static int spacecnt = 0;
rrd_set_error("Cannot parse line '%s'", line);
return 1;
}
-
- j = i;
+
+ j = i;
while (j > 0 && tmpstr[j] != '#')
j--;
+ //see if there is a second color
+ j2 = j-1;
+ while (j2 > 0 && tmpstr[j2] != '#')
+ j2--;
+ if (j && j2) { //yes, swap j and j2, so that j is first color, j2 is second
+ int tmp = j;
+ j = j2;
+ j2 = tmp;
+ tmpstr[j2] = '\0';
+ } else {
+ j2 = 0;
+ }
if (j) {
tmpstr[j] = '\0';
/* We now have:
* tmpstr[0] containing vname
* tmpstr[j] if j!=0 then containing color
- * i size of vname + color
+ * tmpstr[j2] if j2!=0 then containing second color
+ * i size of vname
* j if j!=0 then size of vname
+ * j2 if j2!=0 then size of vname + first color
*/
/* Number or vname ?
dprintf("- parsed color %0.0f,%0.0f,%0.0f,%0.0f\n", gdp->col.red,
gdp->col.green, gdp->col.blue, gdp->col.alpha);
colorfound = 1;
+ if (j2) { //second color?
+ j2++;
+ dprintf("- examining second color '%s'\n", &tmpstr[j2]);
+ //TODO: maybe rrd_parse_color should take a pointer to gdp->col instead of gdp
+ struct gfx_color_t firstcol = gdp->col;
+ if (rrd_parse_color(&tmpstr[j2], gdp)) {
+ rrd_set_error("Could not parse color in '%s'", &tmpstr[j2]);
+ return 1;
+ }
+ dprintf("- parsed color %0.0f,%0.0f,%0.0f,%0.0f\n", gdp->col.red,
+ gdp->col.green, gdp->col.blue, gdp->col.alpha);
+ gdp->col2 = gdp->col;
+ gdp->col = firstcol;
+ //we now have a mandatory grid height
+ (*eaten) += i;
+ if (line[*eaten] != '\0') {
+ (*eaten)++;
+ }
+ dprintf("- examining gradient height\n");
+ i = scan_for_col(&line[*eaten], MAX_VNAME_LEN + 9, tmpstr);
+ sscanf(tmpstr, "%lf%n", &gdp->gradheight, &j);
+ if (i != j) {
+ rrd_set_error("Could not parse gradient height in '%s'", tmpstr);
+ return 1;
+ }
+ dprintf("- parsed gradientheight %0.0f\n", gdp->gradheight);
+ }
} else {
dprintf("- no color present in '%s'\n", tmpstr);
}
case GF_HRULE: /* value#color[:legend] */
case GF_LINE: /* vname-or-value[#color[:legend]][:STACK] */
case GF_AREA: /* vname-or-value[#color[:legend]][:STACK] */
+ case GF_GRAD: /* vname-or-value[#color[:legend][#color[:gradientheight]]][:STACK] */
case GF_TICK: /* vname#color[:num[:legend]] */
if (rrd_parse_PVHLAST(argv[i], &eaten, gdp, im))
return;
case GF_STACK: /* vname-or-value[#color[:legend]] */
if (rrd_parse_PVHLAST(argv[i], &eaten, gdp, im))
return;
- if (last_gf == GF_LINE || last_gf == GF_AREA) {
+ if (last_gf == GF_LINE || last_gf == GF_AREA || last_gf == GF_GRAD) {
gdp->gf = last_gf;
gdp->linewidth = last_linewidth;
} else {