Code

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 a44edcb5a8acfadde135d8508b42ef0acc2b760a..5a73394d23ab14c8b3286fc27cdaa11e6e623faf 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 b1fa64b352d5193c411ef5251b3d274119431393..32ff16e7bd3d22b6c42ef8f76ce82e9a7fd28c26 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 7a6aba4d455bbb1c2a6492d7da36ca26d85de605..35301932a75e0d5be8a75735923ff468879bd505 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 8aca6499b9836d8379cca92653efdaa3a624fc86..bba50440115f9800bd5cf8b29feedc49d9133b4f 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 ab45aafb3f7b12b57d1499667dc1b4dc13f24a13..ac0f3ea6446ca57155bb76535f0fc7ca72984962 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 dd132378ec1f3d59806198f8955ec008683c2266..51f3d3fbae49860ff27c82e0e8e7976174ace330 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 8b9e9ca7fb3f01173847d655fa80149d139ba0b6..d4b37329c4e92c3855f536c147b0526a40d1b107 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') {