2016-10-06 29 views
5

Tôi đã tạo trình kết xuất đường dẫn tùy chỉnh vẽ một mũi tên giữa các nút trong biểu đồ d3 của tôi như được hiển thị trong đoạn mã. Tôi gặp vấn đề cuối cùng mà tôi gặp phải,Làm cách nào để vẽ mũi tên giữa hai điểm trong d3v4?

Làm cách nào để xoay phần mũi tên để nó hướng từ đường cong thay vì hướng của nguồn?

var w2 = 6, 
 
    ar2 = w2 * 2, 
 
    ah = w2 * 3, 
 
    baseHeight = 30; 
 

 
// Arrow function 
 
function CurvedArrow(context, index) { 
 
    this._context = context; 
 
    this._index = index; 
 
} 
 
CurvedArrow.prototype = { 
 
    areaStart: function() { 
 
    this._line = 0; 
 
    }, 
 
    areaEnd: function() { 
 
    this._line = NaN; 
 
    }, 
 
    lineStart: function() { 
 
    this._point = 0; 
 
    }, 
 
    lineEnd: function() { 
 
    if (this._line || (this._line !== 0 && this._point === 1)) { 
 
     this._context.closePath(); 
 
    } 
 
    this._line = 1 - this._line; 
 
    }, 
 
    point: function(x, y) { 
 
    x = +x, y = +y; // jshint ignore:line 
 
    switch (this._point) { 
 
     case 0: 
 
     this._point = 1; 
 
     this._p1x = x; 
 
     this._p1y = y; 
 
     break; 
 
     case 1: 
 
     this._point = 2; // jshint ignore:line 
 
     default: 
 
     var p1x = this._p1x, 
 
      p1y = this._p1y, 
 
      p2x = x, 
 
      p2y = y, 
 
      dx = p2x - p1x, 
 
      dy = p2y - p1y, 
 
      px = dy, 
 
      py = -dx, 
 
      pr = Math.sqrt(px * px + py * py), 
 
      nx = px/pr, 
 
      ny = py/pr, 
 
      dr = Math.sqrt(dx * dx + dy * dy), 
 
      wx = dx/dr, 
 
      wy = dy/dr, 
 
      ahx = wx * ah, 
 
      ahy = wy * ah, 
 
      awx = nx * ar2, 
 
      awy = ny * ar2, 
 
      phx = nx * w2, 
 
      phy = ny * w2, 
 

 
      //Curve figures 
 
      alpha = Math.floor((this._index - 1)/2), 
 
      direction = p1y < p2y ? -1 : 1, 
 
      height = (baseHeight + alpha * 3 * ar2) * direction, 
 

 

 
      //    r5 
 
      //r7   r6|\ 
 
      // ------------ \ 
 
      // ____________ /r4 
 
      //r1   r2|/ 
 
      //    r3 
 

 
      r1x = p1x - phx, 
 
      r1y = p1y - phy, 
 
      r2x = p2x - phx - ahx, 
 
      r2y = p2y - phy - ahy, 
 
      r3x = p2x - awx - ahx, 
 
      r3y = p2y - awy - ahy, 
 
      r4x = p2x, 
 
      r4y = p2y, 
 
      r5x = p2x + awx - ahx, 
 
      r5y = p2y + awy - ahy, 
 
      r6x = p2x + phx - ahx, 
 
      r6y = p2y + phy - ahy, 
 
      r7x = p1x + phx, 
 
      r7y = p1y + phy, 
 
      //Curve 1 
 
      c1mx = (r2x + r1x)/2, 
 
      c1my = (r2y + r1y)/2, 
 
      m1b = (c1mx - r1x)/(r1y - c1my), 
 
      den1 = Math.sqrt(1 + Math.pow(m1b, 2)), 
 
      mp1x = c1mx + height * (1/den1), 
 
      mp1y = c1my + height * (m1b/den1), 
 
      //Curve 2 
 
      c2mx = (r7x + r6x)/2, 
 
      c2my = (r7y + r6y)/2, 
 
      m2b = (c2mx - r6x)/(r6y - c2my), 
 
      den2 = Math.sqrt(1 + Math.pow(m2b, 2)), 
 
      mp2x = c2mx + height * (1/den2), 
 
      mp2y = c2my + height * (m2b/den2); 
 

 
     this._context.moveTo(r1x, r1y); 
 
     this._context.quadraticCurveTo(mp1x, mp1y, r2x, r2y); 
 
     this._context.lineTo(r3x, r3y); 
 
     this._context.lineTo(r4x, r4y); 
 
     this._context.lineTo(r5x, r5y); 
 
     this._context.lineTo(r6x, r6y); 
 
     this._context.quadraticCurveTo(mp2x, mp2y, r7x, r7y); 
 

 
     break; 
 
    } 
 
    } 
 
}; 
 
var w = 600, 
 
    h = 220; 
 
var t0 = Date.now(); 
 

 
var points = [{ 
 
    R: 100, 
 
    r: 3, 
 
    speed: 2, 
 
    phi0: 190 
 
}]; 
 
var path = d3.line() 
 
    .curve(function(ctx) { 
 
    return new CurvedArrow(ctx, 1); 
 
    }); 
 

 
var svg = d3.select("svg"); 
 
var container = svg.append("g") 
 
    .attr("transform", "translate(" + w/2 + "," + h/2 + ")") 
 

 
container.selectAll("g.planet").data(points).enter().append("g") 
 
    .attr("class", "planet").each(function(d, i) { 
 
    d3.select(this).append("circle").attr("r", d.r).attr("cx", d.R) 
 
     .attr("cy", 0).attr("class", "planet"); 
 
    }); 
 
container.append("path"); 
 
var planet = d3.select('.planet circle'); 
 

 
d3.timer(function() { 
 
    var delta = (Date.now() - t0); 
 
    planet.attr("transform", function(d) { 
 
    return "rotate(" + d.phi0 + delta * d.speed/50 + ")"; 
 
    }); 
 

 
    var g = document.createElementNS("http://www.w3.org/2000/svg", "g"); 
 
    g.setAttributeNS(null, "transform", planet.attr('transform')); 
 
    var matrix = g.transform.baseVal.consolidate().matrix; 
 
    svg.selectAll("path").attr('d', function(d) { 
 
    return path([ 
 
     [0, 0], 
 
     [matrix.a * 100, matrix.b * 100] 
 
    ]) 
 
    }); 
 
});
path { 
 
    stroke: #11a; 
 
    fill: #eee; 
 
}
<script src="https://d3js.org/d3.v4.min.js"></script> 
 
<svg width="600" height="220"></svg>

+2

Sẽ không [một cái gì đó như thế này (bằng dấu chọn)] (http://bl.ocks.org/mbostock/1153292) được dễ dàng hơn nhiều? – Mark

+0

Tôi đoán sử dụng một điểm đánh dấu với một cái gì đó như 'orient = auto' sẽ đơn giản hơn vẽ/xoay đầu nhưng tôi không biết làm thế nào khi vẽ trên' canvas' như bạn. Trên một phần tử svg, hãy xem http://bl.ocks.org/tomgp/d59de83f771ca2b6f1d4 ví dụ nếu bạn không nhìn thấy nó (không phải của tôi). – mgc

+1

@ Dấu mốc chỉ trông xấu, chúng khó tùy chỉnh IMO hơn. Khi làm nổi bật, họ sẽ không nhận được đường viền xung quanh chính xác như thế này. Chúng cũng sẽ không hoạt động khi tôi áp dụng gradient cho đường dẫn. – Andrew

Trả lời

2

tôi đã kết thúc làm những gì @ Mark đề nghị trong các ý kiến, tôi tính toán thời điểm đó là chiều cao của đường cong đi dọc theo giữa chừng bình thường giữa hai điểm, sau đó tính toán các đơn vị vectơ từ điểm bắt đầu đến điểm giữa và một lần nữa từ điểm giữa đến cuối. Sau đó tôi có thể sử dụng chúng để có được tất cả các điểm cần thiết.

var arrowRadius = 6, 
 
    arrowPointRadius = arrowRadius * 2, 
 
    arrowPointHeight = arrowRadius * 3, 
 
    baseHeight = 30; 
 

 
// Arrow function 
 
function CurvedArrow(context, index) { 
 
    this._context = context; 
 
    this._index = index; 
 
} 
 
CurvedArrow.prototype = { 
 
    areaStart: function() { 
 
    this._line = 0; 
 
    }, 
 
    areaEnd: function() { 
 
    this._line = NaN; 
 
    }, 
 
    lineStart: function() { 
 
    this._point = 0; 
 
    }, 
 
    lineEnd: function() { 
 
    if (this._line || (this._line !== 0 && this._point === 1)) { 
 
     this._context.closePath(); 
 
    } 
 
    this._line = 1 - this._line; 
 
    }, 
 
    point: function(x, y) { 
 
    x = +x, y = +y; // jshint ignore:line 
 
    switch (this._point) { 
 
     case 0: 
 
     this._point = 1; 
 
     this._p1x = x; 
 
     this._p1y = y; 
 
     break; 
 
     case 1: 
 
     this._point = 2; // jshint ignore:line 
 
     default: 
 
     var p1x = this._p1x, 
 
      p1y = this._p1y, 
 
      p2x = x, 
 
      p2y = y, 
 

 
      //Curve figures 
 

 
      //    mp1 
 
      //    | 
 
      //    | height 
 
      //    | 
 
      // p1 ----------------------- p2 
 
      // 
 
      alpha = Math.floor((this._index - 1)/2), 
 
      direction = p1y < p2y ? -1 : 1, 
 
      height = (baseHeight + alpha * 3 * arrowPointRadius) * direction, 
 
      c1mx = (p2x + p1x)/2, 
 
      c1my = (p2y + p1y)/2, 
 
      m1b = (c1mx - p1x)/(p1y - c1my), 
 
      den1 = Math.sqrt(1 + Math.pow(m1b, 2)), 
 
      // Perpendicular point from the midpoint. 
 
      mp1x = c1mx + height * (1/den1), 
 
      mp1y = c1my + height * (m1b/den1), 
 

 
      // Arrow figures 
 
      dx = p2x - mp1x, 
 
      dy = p2y - mp1y, 
 
      dr = Math.sqrt(dx * dx + dy * dy), 
 
      // Normal unit vectors 
 
      nx = dy/dr, 
 
      wy = nx, 
 
      wx = dx/dr, 
 
      ny = -wx, 
 
      ahx = wx * arrowPointHeight, 
 
      ahy = wy * arrowPointHeight, 
 
      awx = nx * arrowPointRadius, 
 
      awy = ny * arrowPointRadius, 
 
      phx = nx * arrowRadius, 
 
      phy = ny * arrowRadius, 
 

 
      // Start arrow offset. 
 
      sdx = mp1x - p1x, 
 
      sdy = mp1y - p1y, 
 
      spr = Math.sqrt(sdy * sdy + sdx * sdx), 
 
      snx = sdy/spr, 
 
      sny = -sdx/spr, 
 
      sphx = snx * arrowRadius, 
 
      sphy = sny * arrowRadius, 
 

 
      //    r5 
 
      //r7   r6|\ 
 
      // ------------ \ 
 
      // ____________ /r4 
 
      //r1   r2|/ 
 
      //    r3 
 

 
      r1x = p1x - sphx, 
 
      r1y = p1y - sphy, 
 
      r2x = p2x - phx - ahx, 
 
      r2y = p2y - phy - ahy, 
 
      r3x = p2x - awx - ahx, 
 
      r3y = p2y - awy - ahy, 
 
      r4x = p2x, 
 
      r4y = p2y, 
 
      r5x = p2x + awx - ahx, 
 
      r5y = p2y + awy - ahy, 
 
      r6x = p2x + phx - ahx, 
 
      r6y = p2y + phy - ahy, 
 
      r7x = p1x + sphx, 
 
      r7y = p1y + sphy, 
 
      mpc1x = mp1x - phx, 
 
      mpc1y = mp1y - phy, 
 
      mpc2x = mp1x + phx, 
 
      mpc2y = mp1y + phy; 
 

 
     this._context.moveTo(r1x, r1y); 
 
     this._context.quadraticCurveTo(mpc1x, mpc1y, r2x, r2y); 
 
     this._context.lineTo(r3x, r3y); 
 
     this._context.lineTo(r4x, r4y); 
 
     this._context.lineTo(r5x, r5y); 
 
     this._context.lineTo(r6x, r6y); 
 
     this._context.quadraticCurveTo(mpc2x, mpc2y, r7x, r7y); 
 
     this._context.closePath(); 
 

 
     break; 
 
    } 
 
    } 
 
}; 
 

 
var w = 600, 
 
    h = 220; 
 
var t0 = Date.now(); 
 

 
var points = [{ 
 
    R: 100, 
 
    r: 3, 
 
    speed: 2, 
 
    phi0: 190 
 
}]; 
 
var path = d3.line() 
 
    .curve(function(ctx) { 
 
    return new CurvedArrow(ctx, 1); 
 
    }); 
 

 
var svg = d3.select("svg"); 
 
var container = svg.append("g") 
 
    .attr("transform", "translate(" + w/2 + "," + h/2 + ")") 
 

 
container.selectAll("g.planet").data(points).enter().append("g") 
 
    .attr("class", "planet").each(function(d, i) { 
 
    d3.select(this).append("circle").attr("r", d.r).attr("cx", d.R) 
 
     .attr("cy", 0).attr("class", "planet"); 
 
    }); 
 
container.append("path"); 
 
var planet = d3.select('.planet circle'); 
 

 
d3.timer(function() { 
 
    var delta = (Date.now() - t0); 
 
    planet.attr("transform", function(d) { 
 
    return "rotate(" + d.phi0 + delta * d.speed/50 + ")"; 
 
    }); 
 

 
    var g = document.createElementNS("http://www.w3.org/2000/svg", "g"); 
 
    g.setAttributeNS(null, "transform", planet.attr('transform')); 
 
    var matrix = g.transform.baseVal.consolidate().matrix; 
 
    svg.selectAll("path").attr('d', function(d) { 
 
    return path([ 
 
     [0, 0], 
 
     [matrix.a * 100, matrix.b * 100] 
 
    ]) 
 
    }); 
 
});
path { 
 
    stroke: #11a; 
 
    fill: #eee; 
 
}
<script src="https://d3js.org/d3.v4.min.js"></script> 
 
<svg width="600" height="220"></svg>

+0

Rất hay. Tôi đã làm việc trên cùng một [ở đây] (http://plnkr.co/edit/eG1dtD1MHBo3vyTdgcKN?p=preview), bạn đánh bại tôi với nó :) – Mark

Các vấn đề liên quan