问题描述
我使用强制布局来创建有向图。
它在画布上呈现。我的示例示例位于
我的代码:
$ b
我使用强制布局来创建有向图。
它在画布上呈现。我的示例示例位于
我的代码:
$ b
基本问题
这两点需要是随机的(从任何地方到任何地方)任何地方)x1,y1和x2,y2。您将需要控制弯曲量,这些弯曲量与点之间的距离不变(即,如果点之间的距离为100像素或10像素,则弯曲量相同)。
x1,y1 //作为开始
x2,y2 //作为结束
弯曲//作为点之间的距离因子
//负向弯曲(向右)
//正向弯曲(到线左侧)
arrowLen //以像素为单位
arrowWidth //以像素为单位,
arrowStart //布尔如果开始时为箭头
arrowEnd //布尔如果箭头结束。
我假设您希望线条从一个圆圈到另一个圆圈。因此你想要指定圆心和圆的半径。这需要两个额外的参数,一个用于起始圆的半径,另一个用于结束。
还有两个点接近两个时应该怎么做的问题即它们重叠)。除了如果不适合绘制线条和箭头之外,没有真正的解决方案。
演示必须随着时间的推移而改变大小的圆形,共有6个弧,其弯曲值分别为0.1,0.3,0.6和-0.1,-0.3,-0.6。
完成这个操作的函数被称为 drawBend ,并且我已经把在那里有很多评论,还有一些注释行可以让你改变当开始和结束之间的距离改变时弧的变化。如果取消一个注释,设置变量 b1 ,您必须注释掉其他作业
更新我发现了一个更好的找到圆弧半径和中心点的方法。对称性提供了一组非常方便的类似三角形,因此我可以将函数缩短9行。我已经更新了演示。
弧线被画成笔画,箭头被填充。
它的速度相当快,但如果您计划实时绘制多条100条曲线,则可以通过将曲线从头到尾进行优化,然后共享一些计算。如果交换开始和结束,并且有许多值保持不变,则从开始到结束的弧将以另一种方式弯曲,因此可以在绘制2的CPU负载大约75%时获得两个弧度
I am using forced layout to create directed graph .Its rendered on canvas . My sample example is at http://jsbin.com/vuyapibaqa/1/edit?html,output
Now I am inspired from
https://bl.ocks.org/mattkohl/146d301c0fc20d89d85880df537de7b0#index.html
Few Resources in d3 svg , something similar i am trying to get in canvas.
http://jsfiddle.net/zhanghuancs/a2QpA/
http://bl.ocks.org/mbostock/1153292 https://bl.ocks.org/ramtob/3658a11845a89c4742d62d32afce3160
http://bl.ocks.org/thomasdobber/9b78824119136778052f64a967c070e0Drawing multiple edges between two nodes with d3.
Want to add elliptical arc connecting edge with arrow . How to achieve this in canvas.
My Code :
<!DOCTYPE html> <html> <head> <title>Sample Graph Rendring Using Canvas</title> <script src="https://rawgit.com/gka/randomgraph.js/master/randomgraph.js"></script> <script src="https://d3js.org/d3.v4.min.js"></script> </head> <body> <script> 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(d.x, d.y); var r = d3.select(this).select("circle").node().getAttribute('r'); d.x = Math.max(30, Math.min(width - 30, d.x)); d.y = Math.max(30, Math.min(height - 30, d.y)); context.closePath(); context.arc(d.x, d.y, 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(d.x + 15, d.y-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),d.x + 10, d.y-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 *****/ </script> </body> </html>
The two points need to be random(from anywhere to anywhere) x1,y1 and x2,y2. You will want to control the amount of bending that is invariant to the distance between the points (ie the same amount of bending if the distance between points is 100 pixels or 10 pixels)
Thus inputs are
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.
I assume you want the lines to be from one circle to the next. Thus you want to specify the circle centers and the radius of the circles. This will require two additional arguments one for the start circle radius and one for the end.
There is also the problem of what to do when the two points are two close (ie they overlap). There is not real solution apart from not to draw the lines and arrows if they don't fit.
The demo has to circles that change size over time, there are 6 arcs with different bend values of 0.1,0.3, 0.6 and -0.1, -0.3, -0.6. Move the mouse to change end circles position.
The function that does it all is called drawBend and I have put a lot of comments in there, There is also some commented lines that let you change how the arcs change when the distance between start and end changes. If you uncomment one, setting the variable b1 you MUST comment out the other assignments
Update I have found a much better method of finding the arc radius and thus center point. The symmetry provided a very convenient set of similar triangles and and thus I could shorten the function by 9 lines. I have updated the demo.
The arc is draw as a stroke, and the arrowheads as a fill.
Its reasonably quick, but if you plan to draw many 100's in real-time you can optimise by having the arc from and then back share some calcs. The arc from start to end will bend the other way if you swap the start and end, and there are many values that remain unchanged, so you can get two arcs for about a 75% CPU load of drawing 2
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; }
<canvas id="canvas"></canvas>
这篇关于椭圆弧箭头边缘d3强制布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!