support for dashed lines in graphs
authoroetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa>
Wed, 2 Jan 2008 22:11:26 +0000 (22:11 +0000)
committeroetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa>
Wed, 2 Jan 2008 22:11:26 +0000 (22:11 +0000)
git-svn-id: svn://svn.oetiker.ch/rrdtool/trunk/program@1262 a5681a0c-68f1-0310-ab6d-d61299d08faa

CONTRIBUTORS
NEWS
doc/rrdgraph_examples.pod
doc/rrdgraph_graph.pod
src/rrd_graph.c
src/rrd_graph.h
src/rrd_graph_helper.c

index a44edcb..5a73394 100644 (file)
@@ -62,6 +62,7 @@ Steve Harris <steveh with wesley.com.au> AIX portability
 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>
diff --git a/NEWS b/NEWS
index b1fa64b..32ff16e 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,7 @@ Graphing (Tobi Oetiker)
 * --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)
 -----------
index 7a6aba4..3530193 100644 (file)
@@ -99,6 +99,22 @@ Note: the second line gets stacked on top of the first one
 
 =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
index 8aca649..bba5044 100644 (file)
@@ -12,11 +12,11 @@ B<GPRINT>B<:>I<vname>B<:>I<format>
 
 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>]]
 
@@ -221,21 +221,23 @@ Text is printed literally in the legend section of the graph. Note that in
 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
@@ -246,6 +248,14 @@ B<VDEF>, and B<CDEF>.  If the optional B<STACK> modifier is used, this line
 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.
 
index ab45aaf..ac0f3ea 100644 (file)
@@ -322,6 +322,10 @@ int im_free(
                 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);
     }
@@ -2600,6 +2604,11 @@ void grid_paint(
                                       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);
             }
@@ -3120,6 +3129,12 @@ int graph_paint(
                     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
@@ -3180,10 +3195,14 @@ int graph_paint(
                     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++) {
@@ -3327,19 +3346,33 @@ int graph_paint(
         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;
@@ -3415,6 +3448,7 @@ int gdes_alloc(
     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;
@@ -3427,7 +3461,6 @@ int gdes_alloc(
     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;
index dd13237..51f3d3f 100644 (file)
@@ -171,6 +171,13 @@ typedef struct graph_desc_t {
     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;
 
index 8b9e9ca..d4b3732 100644 (file)
@@ -174,7 +174,7 @@ int rrd_parse_find_gf(
     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);
@@ -183,7 +183,7 @@ int rrd_parse_find_gf(
                               &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",
@@ -740,28 +740,110 @@ int rrd_parse_PVHLAST(
     (*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') {