椭圆弧箭头边缘d3强制布局

我正在使用强制布局来创建有向图。 它在canvas上呈现。 我的示例示例位于http://jsbin.com/vuyapibaqa/1/edit?html,output

现在我受到了启发
https://bl.ocks.org/mattkohl/146d301c0fc20d89d85880df537de7b0#index.html

d3 svg中的资源很少,我试图在canvas中使用类似的东西。

http://jsfiddle.net/zhanghuancs/a2QpA/

http://bl.ocks.org/mbostock/1153292 https://bl.ocks.org/ramtob/3658a11845a89c4742d62d32afce3160
http://bl.ocks.org/thomasdobber/9b78824119136778052f64a967c070e0 使用d3在两个节点之间绘制多条边 。

想要用箭头添加椭圆弧连接边。 如何在canvas中实现这一点。

在此处输入图像描述

我的代码:

   Sample Graph Rendring Using Canvas      var graph = {}//randomgraph.WattsStrogatz.beta(15, 4, 0.06); graph.nodes = [{"label":"x"} , {"label":"y"}]; graph.edges = [{source:0,target:1},{source:0,target:1}, {source:1,target:0}] var canvas = null var width = window.innerWidth, height = window.innerHeight; canvas = d3.select("body").append("canvas").attr("width",width).attr("height",height); var context = canvas.node().getContext("2d"); force = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.index; })).force("charge", d3.forceManyBody()) .force("center", d3.forceCenter(width / 2, height / 2)); force.nodes(graph.nodes); force.force("link").links(graph.edges).distance(200); var detachedContainer = document.createElement("custom"); dataContainer = d3.select(detachedContainer); link = dataContainer.selectAll(".link").data(graph.edges) .enter().append("line").attr("class", "link") .style("stroke-width", 2) node = dataContainer.selectAll(".node").data(graph.nodes) .enter().append("g"); var circles = node.append("circle") .classed("circle-class", true) .attr("class", function (d){ return "node node_" + d.index;}) .attr("r", 5) .attr("fill", 'red') .attr("strokeStyle", 'black'); d3.timer(function(){ context.clearRect(0, 0, width, height); // draw links link.each(function(d) { context.strokeStyle = "#ccc"; /***** Elliptical arcs *****/ context.stroke(new Path2D(linkArc(d))); /***** Elliptical arcs *****/ }); context.lineWidth = 2; node.each(function(d) { context.beginPath(); context.moveTo(dx, dy); var r = d3.select(this).select("circle").node().getAttribute('r'); dx = Math.max(30, Math.min(width - 30, dx)); dy = Math.max(30, Math.min(height - 30, dy)); context.closePath(); context.arc(dx, dy, r, 0, 2 * Math.PI); context.fillStyle = d3.select(this).select("circle").node().getAttribute('fill'); context.strokeStyle = d3.select(this).select("circle").node().getAttribute('strokeStyle'); context.stroke(); context.fill(); context.beginPath(); context.arc(dx + 15, dy-20, 5, 0, 2 * Math.PI); context.fillStyle = "orange"; context.strokeStyle = "orange"; var data = d3.select(this).data(); context.stroke(); context.fill(); context.font = "10px Arial"; context.fillStyle = "black"; context.strokeStyle = "black"; context.fillText(parseInt(data[0].index),dx + 10, dy-15); }); }); circles.transition().duration(5000).attr('r', 20).attr('fill', 'orange'); canvas.node().addEventListener('click',function( event ){ console.log(event) // Its COMING ANY TIME INSIDE ON CLICK OF CANVAS }); /***** Elliptical arcs *****/ function linkArc(d) { var dx = d.target.x - d.source.x, dy = d.target.y - d.source.y, dr = Math.sqrt(dx * dx + dy * dy); return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y; } /***** Elliptical arcs *****/    

用箭头将圆弧绘制成圆形

基本问题

这两点需要是随机的(从任何地方到任何地方)x1,y1和x2,y2。 您将需要控制与点之间的距离不变的弯曲量(即,如果点之间的距离为100像素或10像素,则弯曲量相同)

因此投入是

 x1,y1 // as start x2,y2 // as end bend // as factor of distance between points // negative bends up (to right) // positive bends down (to left of line) arrowLen // in pixels arrowWidth // in pixels, arrowStart // boolean if arrow at start arrowEnd // boolean if arrow at end. 

基本方法步骤

  1. 找到两个终点之间的中点。
  2. 获得点之间的距离
  3. 从头到尾获取规范化的向量。
  4. 旋转标准90deg
  5. 通过旋转范数乘以弯曲的距离,并添加到中点以找到弧上的中点
  6. 使用3个点找到适合所有3个点的圆的半径。
  7. 使用半径查找弧的中心
  8. 从中心找到方向到开始和结束
  9. 现在我们有半径,使用箭头len查找箭头的角度长度
  10. 从箭头内部或开始/结束绘制弧(取决于显示的箭头)
  11. 从弧形中心沿线平面绘制箭头

其他问题。

我假设您希望线条从一个圆圈到下一个圆圈。 因此,您需要指定圆心和圆的半径。 这将需要两个额外的参数,一个用于起始圆半径,一个用于结束。

当两个点两个接近(即它们重叠)时,还存在要做什么的问题。 除了不绘制线条和箭头(如果它们不适合)之外,没有真正的解决方案。

作为演示的解决方案

该演示必须包含随时间改变大小的圆,有6个弧,其不同的弯曲值为0.1,0.3,0.6和-0.1,-0.3,-0.6。 移动鼠标以更改结束圆圈位置。

完成这一切的函数叫做drawBend ,我在那里放了很多注释。还有一些注释行可以让你改变当开始和结束之间的距离发生变化时弧的变化方式。 如果你取消注释一个,设置变量b1 (你指定给x3,y3是弧上的中点)你必须注释掉其他的赋值

找到圆弧半径和圆心的解决方案很复杂,并且由于对称性,很可能是更好的解决方案。 该部分将找到适合任何3个点的圆圈(如果不是全部在一条线上),那么可能还有其他用途。

更新我找到了一种更好的方法来找到圆弧半径,从而找到中心点。 对称性提供了一组非常方便的类似三角形,因此我可以将函数缩短9行。 我已经更新了演示。

弧形绘制为笔划,箭头绘制为填充。

它的速度相当快,但是如果你打算实时绘制100多个,你可以通过使用弧来优化,然后再分享一些计算。 如果交换开始和结束,从开始到结束的弧将以另一种方式弯曲,并且有许多值保持不变,因此您可以获得两个弧,用于绘制2的大约75%的CPU负载

 const ctx = canvas.getContext("2d"); const mouse = {x : 0, y : 0, button : false} function mouseEvents(e){ mouse.x = e.pageX; mouse.y = e.pageY; mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button; } ["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents)); // x1,y1 location of a circle start // x2,y2 location of the end circle // bend factor. negative bends up for, positive bends down. If zero the world will end // aLen is Arrow head length in pixels // aWidth is arrow head width in pixels // sArrow boolean if true draw start arrow // eArrow boolean if true draw end arrow // startRadius = radius of a circle if start attached to circle // endRadius = radius of a circle if end attached to circle function drawBend(x1, y1, x2, y2, bend, aLen, aWidth, sArrow, eArrow, startRadius, endRadius){ var mx, my, dist, nx, ny, x3, y3, cx, cy, radius, vx, vy, a1, a2; var arrowAng,aa1,aa2,b1; // find mid point mx = (x1 + x2) / 2; my = (y1 + y2) / 2; // get vector from start to end nx = x2 - x1; ny = y2 - y1; // find dist dist = Math.sqrt(nx * nx + ny * ny); // normalise vector nx /= dist; ny /= dist; // The next section has some optional behaviours // that set the dist from the line mid point to the arc mid point // You should only use one of the following sets //-- Uncomment for behaviour of arcs // This make the lines flatten at distance //b1 = (bend * 300) / Math.pow(dist,1/4); //-- Uncomment for behaviour of arcs // Arc bending amount close to constant // b1 = bend * dist * 0.5 b1 = bend * dist // Arc amount bend more at dist x3 = mx + ny * b1; y3 = my - nx * b1; // get the radius radius = (0.5 * ((x1-x3) * (x1-x3) + (y1-y3) * (y1-y3)) / (b1)); // use radius to get arc center cx = x3 - ny * radius; cy = y3 + nx * radius; // radius needs to be positive for the rest of the code radius = Math.abs(radius); // find angle from center to start and end a1 = Math.atan2(y1 - cy, x1 - cx); a2 = Math.atan2(y2 - cy, x2 - cx); // normalise angles a1 = (a1 + Math.PI * 2) % (Math.PI * 2); a2 = (a2 + Math.PI * 2) % (Math.PI * 2); // ensure angles are in correct directions if (bend < 0) { if (a1 < a2) { a1 += Math.PI * 2 } } else { if (a2 < a1) { a2 += Math.PI * 2 } } // convert arrow length to angular len arrowAng = aLen / radius * Math.sign(bend); // get angular length of start and end circles and move arc start and ends a1 += startRadius / radius * Math.sign(bend); a2 -= endRadius / radius * Math.sign(bend); aa1 = a1; aa2 = a2; // check for too close and no room for arc if ((bend < 0 && a1 < a2) || (bend > 0 && a2 < a1)) { return; } // is there a start arrow if (sArrow) { aa1 += arrowAng } // move arc start to inside arrow // is there an end arrow if (eArrow) { aa2 -= arrowAng } // move arc end to inside arrow // check for too close and remove arrows if so if ((bend < 0 && aa1 < aa2) || (bend > 0 && aa2 < aa1)) { sArrow = false; eArrow = false; aa1 = a1; aa2 = a2; } // draw arc ctx.beginPath(); ctx.arc(cx, cy, radius, aa1, aa2, bend < 0); ctx.stroke(); ctx.beginPath(); // draw start arrow if needed if(sArrow){ ctx.moveTo( Math.cos(a1) * radius + cx, Math.sin(a1) * radius + cy ); ctx.lineTo( Math.cos(aa1) * (radius + aWidth / 2) + cx, Math.sin(aa1) * (radius + aWidth / 2) + cy ); ctx.lineTo( Math.cos(aa1) * (radius - aWidth / 2) + cx, Math.sin(aa1) * (radius - aWidth / 2) + cy ); ctx.closePath(); } // draw end arrow if needed if(eArrow){ ctx.moveTo( Math.cos(a2) * radius + cx, Math.sin(a2) * radius + cy ); ctx.lineTo( Math.cos(aa2) * (radius - aWidth / 2) + cx, Math.sin(aa2) * (radius - aWidth / 2) + cy ); ctx.lineTo( Math.cos(aa2) * (radius + aWidth / 2) + cx, Math.sin(aa2) * (radius + aWidth / 2) + cy ); ctx.closePath(); } ctx.fill(); } /** SimpleUpdate.js begin **/ // short cut vars var w = canvas.width; var h = canvas.height; var cw = w / 2; // center var ch = h / 2; var globalTime = new Date().valueOf(); // global to this // main update function function update(timer){ globalTime = timer; if(w !== innerWidth || h !== innerHeight){ // resize if needed cw = (w = canvas.width = innerWidth) / 2; ch = (h = canvas.height = innerHeight) / 2; } ctx.setTransform(1,0,0,1,0,0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0,0,w,h); var startRad = (Math.sin(timer / 2000) * 0.5 + 0.5) * 20 + 5; var endRad = (Math.sin(timer / 7000) * 0.5 + 0.5) * 20 + 5; ctx.lineWidth = 2; ctx.fillStyle = "white"; ctx.strokeStyle = "black"; ctx.beginPath(); ctx.arc(cw,ch,startRad,0,Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.arc(mouse.x,mouse.y,endRad,0,Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.lineWidth = 2; ctx.fillStyle = "black"; ctx.strokeStyle = "black"; drawBend(cw,ch,mouse.x,mouse.y,-0.1,10,10,true,true,startRad + 1,endRad + 1); drawBend(cw,ch,mouse.x,mouse.y,-0.3,10,10,true,true,startRad + 1,endRad + 1); drawBend(cw,ch,mouse.x,mouse.y,-0.6,10,10,true,true,startRad + 1,endRad + 1); drawBend(cw,ch,mouse.x,mouse.y,0.1,10,10,true,true,startRad + 1,endRad + 1); drawBend(cw,ch,mouse.x,mouse.y,0.3,10,10,true,true,startRad + 1,endRad + 1); drawBend(cw,ch,mouse.x,mouse.y,0.6,10,10,true,true,startRad + 1,endRad + 1); requestAnimationFrame(update); } requestAnimationFrame(update); 
 canvas { position : absolute; top : 0px; left : 0px; }