]> granicus.if.org Git - graphviz/commitdiff
Add trial implementation of serially multicolored edges
authorerg <devnull@localhost>
Mon, 19 Jul 2010 20:22:35 +0000 (20:22 +0000)
committererg <devnull@localhost>
Mon, 19 Jul 2010 20:22:35 +0000 (20:22 +0000)
lib/common/emit.c

index a1aa36cfa625e8e2890d1def1d18b471acc713d4..c2e6287ed0ad0ae76de2cf944dcc80e0b0d7abc1 100644 (file)
@@ -1460,9 +1460,212 @@ static char* default_pencolor(char *pencolor, char *deflt)
     return buf;
 }
 
+typedef struct {
+    char* color;
+    float t;
+} colorseg_t;
+static void
+freeSegs (colorseg_t* segs)
+{
+    free (segs->color);
+    free (segs);
+}
+
+/* getSegLen:
+ * Find comma in s, replace with '\0'.
+ * Convert remainder to float v and verify prev_v < v < 1.
+ * Return 0 on failure
+ */
+static float getSegLen (char* s, float prev_v)
+{
+    char* p = strchr (s, ',');
+    char* endp;
+    float v;
+
+    if (!p) return 0;
+    *p++ = '\0';
+    v = strtof (p, &endp);
+    if ((endp != p) && (prev_v < v) && (v < 1))
+       return v;
+    else
+       return 0; 
+}
+
+/* parseSegs:
+ * Parse string of form color,float:color,float:...:color,float:color
+ * where the floats are positive, < 1, and monotonically increasing.
+ * Store the values in an array of colorseg_t's and return the array.
+ * Return NULL on error.
+ */
+static colorseg_t*
+parseSegs (char* clrs, int nseg)
+{
+    char* colors = strdup (clrs);
+    colorseg_t* segs = N_NEW(nseg+1,colorseg_t);
+    char* color;
+    int cnum;
+    float v, prev_v = 0;
+
+    for (cnum = 0, color = strtok(colors, ":"); color; cnum++, color = strtok(0, ":")) {
+       if (cnum == nseg-1) {
+           segs[cnum].color = color;
+           segs[cnum].t = 1.0;
+       }
+       else if ((v = getSegLen (color, prev_v)) != 0) {
+           segs[cnum].color = color;
+           segs[cnum].t = (v - prev_v)/(1.0 - prev_v);
+           prev_v = v;
+       }
+       else {
+           agerr (AGERR, "Invalid color spec \"%s\"", clrs);
+           if (cnum == 0)
+               free (colors);
+           freeSegs (segs);
+           return NULL;
+       }
+    }
+    
+    return segs;
+}
+
+/* approxLen:
+ */
+static double approxLen (pointf* pts)
+{
+    double d = DIST(pts[0],pts[1]);
+    d += DIST(pts[1],pts[2]);
+    d += DIST(pts[2],pts[3]);
+    return d;
+}
+/* splitBSpline:
+ * Given B-spline bz and 0 < t < 1, split bz so that left corresponds to
+ * the fraction t of the arc length. The new parts are store in left and right.
+ * The caller needs to free the allocated points.
+ *
+ * In the current implementation, we find the Bezier that should contain t by
+ * treating the control points as a polyline.
+ * We then split that Bezier.
+ */
+static void splitBSpline (bezier* bz, float t, bezier* left, bezier* right)
+{
+    int i, j, k, cnt = (bz->size - 1)/3;
+    double* lens;
+    double last, len, sum;
+    pointf* pts;
+    float r;
+
+    if (cnt == 1) {
+       left->size = 4;
+       left->list = N_NEW(4, pointf);
+       right->size = 4;
+       right->list = N_NEW(4, pointf);
+       Bezier (bz->list, 3, t, left->list, right->list);
+       return;
+    }
+    
+    lens = N_NEW(cnt, double);
+    sum = 0;
+    pts = bz->list;
+    for (i = 0; i < cnt; i++) {
+       lens[i] = approxLen (pts);
+       sum += lens[i];
+       pts += 3;
+    }
+    len = t*sum;
+    sum = 0;
+    for (i = 0; i < cnt; i++) {
+       sum += lens[i];
+       if (sum >= len)
+           break;
+    }
+
+    left->size = 3*(i+1) + 1;
+    left->list = N_NEW(left->size,pointf);
+    right->size = 3*(cnt-i) + 1;
+    right->list = N_NEW(right->size,pointf);
+    for (j = 0; j < left->size; j++)
+       left->list[j] = bz->list[j];
+    k = j - 4;
+    for (j = 0; j < right->size; j++)
+       right->list[j] = bz->list[k++];
+
+    last = lens[i];
+    r = (len - (sum - last))/last;
+    Bezier (bz->list + 3*i, 3, r, left->list + 3*i, right->list);
+
+    free (lens);
+}
+
+/* multicolor:
+ * Draw an edge as a sequence of colors.
+ * Not sure how to handle multiple B-splines, so do a naive
+ * implementation.
+ * Return non-zero if color spec is incorrect
+ */
+static int multicolor (GVJ_t * job, edge_t * e, char** styles, char* colors, int num, double arrowsize, double penwidth)
+{ 
+    bezier bz;
+    bezier bz0, bz_l, bz_r;
+    int i;
+    colorseg_t* segs = parseSegs (colors, num);
+    colorseg_t* s;
+    char* endcolor;
+
+    if (segs == NULL) {
+       Agraph_t* g = e->tail->graph;
+       agerr (AGPREV, "in edge %s%s%s\n", agnameof(e->tail), (AG_IS_DIRECTED(g)?" -> ":" -- "), agnameof(e->head));
+       return 1;
+    }
+
+    for (i = 0; i < ED_spl(e)->size; i++) {
+       bz = ED_spl(e)->list[i];
+       for (s = segs; s->color; s++) {
+           gvrender_set_pencolor(job, s->color);
+           if (s == segs) {
+               splitBSpline (&bz, s->t, &bz_l, &bz_r);
+               gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, FALSE, FALSE);
+               free (bz_l.list);
+           }
+           else if (s->t < 1.0) {
+               bz0 = bz_r;
+               splitBSpline (&bz0, s->t, &bz_l, &bz_r);
+               free (bz0.list);
+               gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, bz.eflag, FALSE);
+               free (bz_l.list);
+           }
+           else {
+               endcolor = s->color;
+               gvrender_beziercurve(job, bz_r.list, bz_r.size, FALSE, FALSE, FALSE);
+               free (bz_r.list);
+           }
+               
+       }
+                /* arrow_gen resets the job style  (How?  FIXME)
+                 * If we have more splines to do, restore the old one.
+                 * Use local copy of penwidth to work around reset.
+                 */
+       if (bz.sflag) {
+           gvrender_set_pencolor(job, segs->color);
+           gvrender_set_fillcolor(job, segs->color);
+           arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
+       }
+       if (bz.eflag) {
+           gvrender_set_pencolor(job, endcolor);
+           gvrender_set_fillcolor(job, endcolor);
+           arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
+       }
+       if ((ED_spl(e)->size>1) && (bz.sflag||bz.eflag) && styles) 
+           gvrender_set_style(job, styles);
+    }
+    freeSegs (segs);
+    return 0;
+}
+
 static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
 {
-    int i, j, cnum, numc = 0;
+    int i, j, cnum, numc = 0, numcomma = 0;
     char *color, *pencolor, *fillcolor;
     char *headcolor, *tailcolor, *lastcolor;
     char *colors = NULL;
@@ -1480,9 +1683,20 @@ static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
        color = late_string(e, E_color, "");
 
        /* need to know how many colors separated by ':' */
-       for (p = color; *p; p++)
+       for (p = color; *p; p++) {
            if (*p == ':')
                numc++;
+           else if (*p == ',')
+               numcomma++;
+       }
+
+       if (numcomma && numc) {
+           if (multicolor (job, e, styles, color, numc+1, arrowsize, penwidth)) {
+               color = DEFAULT_COLOR;
+           }
+           else
+               return;
+       }
 
        fillcolor = pencolor = color;
        if (ED_gui_state(e) & GUI_STATE_ACTIVE) {