本文介绍了椭圆弧箭头边缘d3强制布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用强制布局来创建有向图。
它在画布上呈现。我的示例示例位于



我的代码:
$ b

<!DOCTYPE html>< html>< head>< html>> ; < title>使用画布渲染图形的示例图< / 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> <脚本> 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( 链接)链路(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){returnnode node_+ d.index;}).attr( r,5).attr(fill,red).attr(strokeStyle,'black'); d3.timer(function(){context.clearRect(0,0,width,height); //绘制链接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'); con text.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); }); }); ('',20).attr('fill','orange'); canvas.node()。addEventListener('click',function(event){console.log(event)//它的任何时间都在点击画布}); / *****椭圆弧***** /函数linkArc(d){var dx = d.target.x - d.source.x,dy = d.target.y - d.source.y,dr = Math.sqrt(dx * dx + dy * dy);返回M+ d.source.x +,+ d.source.y +A+ dr +,+ dr +0 0,1+ d.target.x +,+ d.target.y; } / *****椭圆弧***** /< / script>< / body>< / html>

基本问题

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


  x1,y1 //作为开始
x2,y2 //作为结束
弯曲//作为点之间的距离因子
//负向弯曲(向右)
//正向弯曲(到线左侧)
arrowLen //以像素为单位
arrowWidth //以像素为单位,
arrowStart //布尔如果开始时为箭头
arrowEnd //布尔如果箭头结束。



基本方法步骤




  1. 找到两个端点之间的中点。
  2. 获得点之间的距离
  3. 获取从开始到结束的归一化向量。

  4. 旋转标准90deg

  5. 通过旋转标准弯曲乘以距离并添加到中点以找到圆弧上的中点
  6. 使用3个点找到适合所有3个点的圆的半径。
  7. 使用半径查找圆弧的中心
  8. 从中心找到开始和结束的方向

  9. 使用箭头len查找箭头的角度长度,现在我们有半径了

  10. 从内部箭头或开始/结束绘制圆弧(取决于显示的箭头)
  11. 从圆心中沿着直线的点绘制箭头



    1. 其他问题。



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



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



      解决方案为演示



      演示必须随着时间的推移而改变大小的圆形,共有6个弧,其弯曲值分别为0.1,0.3,0.6和-0.1,-0.3,-0.6。

      完成这个操作的函数被称为 drawBend ,并且我已经把在那里有很多评论,还有一些注释行可以让你改变当开始和结束之间的距离改变时弧的变化。如果取消一个注释,设置变量 b1 ,您必须注释掉其他作业



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



      弧线被画成笔画,箭头被填充。

      它的速度相当快,但如果您计划实时绘制多条100条曲线,则可以通过将曲线从头到尾进行优化,然后共享一些计算。如果交换开始和结束,并且有许多值保持不变,则从开始到结束的弧将以另一种方式弯曲,因此可以在绘制2的CPU负载大约75%时获得两个弧度



      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圆的位置开始// x2,y2结束圈的位置//弯曲因子。负面消退,积极向下。如果零世界将结束// aLen是以像素为单位的箭头长度// aWidth是以像素为单位的箭头宽度// sArrow布尔如果为真绘制开始箭头// eArrow布尔如果为true绘制结束箭头// startRadius =圆的半径如果开始附加到圆圈// endRadius =圆圈的半径,如果结束附加到circle函数drawBend(x1,y1,x2,y2,bend,aLen,aWidth,sArrow,eArrow,startRadius,endRadius){var mx,my,dist, nx,ny,x3,y3,cx,cy,半径,vx,vy,a1,a2; var arrowAng,aa1,aa2,b1; //找到中点mx =(x1 + x2)/ 2; my =(y1 + y2)/ 2; //从头到尾获取矢量nx = x2 - x1; ny = y2-y1; //找到dist dist = Math.sqrt(nx * nx + ny * ny); // normalize vector nx / = dist; ny / = dist; //下一部分有一些可选行为//将dist从行中点设置为圆弧中点//您只应使用以下其中一个设置// - 取消注释行为的弧// //这会使线距离变平// b1 =(bend * 300)/ Math.pow(dist,1/4); // - 取消注释弧的行为//弧弯曲量接近常数// b1 =弯曲* dist * 0.5 b1 =弯曲* dist //在弯矩x3 = mx + ny * b1时弧的弯曲更多; y3 = my-nx * b1; //得到半径半径=(0.5 *((x1-x3)*(x1-x3)+(y1-y3)*(y1-y3))/(b1)); //使用半径得到圆心cx = x3 - ny * radius; cy = y3 + nx * radius; //半径需要为代码的其余部分为正半径= Math.abs(radius); //从中心开始和结束角度a1 = Math.atan2(y1 - cy,x1 - cx); a2 = Math.atan2(y2-cy,x2-cx); //规格化角度a1 =(a1 + Math.PI * 2)%(Math.PI * 2); a2 =(a2 + Math.PI * 2)%(Math.PI * 2); //如果(弯曲 canvas {position:绝对; top:0px; left:0px; } < canvas id =canvas><<<< ; / canvas>


      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>  

      解决方案

      Draw arc from circle to circle with arrow heads

      The basic problem

      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.
      

      Basic method steps

      1. Find the mid-point between the two end points.
      2. Get distance between points
      3. Get the normalised vector from start to end.
      4. Rotate norm 90deg
      5. Multiply distance by bend by rotated norm and add to mid point to find mid point on arc
      6. With the 3 points find the radius of a circle that will fit all 3 points.
      7. Use the radius to find the center of the arc
      8. From the center find the direction to the start and end
      9. Use the arrow len to find angular length of arrows now we have the radius
      10. Draw the arc from inside arrows or start / end (depending if arrows shown)
      11. Draw arrow from point with flat side along line from arc center

      Additional problems.

      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 Solution as a Demo

      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强制布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-21 05:38