D3.js使用画布强制布局,并在末尾强制使用箭头链接

时间:2015-03-07 10:35:38

标签: javascript canvas d3.js

当我使用canvas和D3.JS绘制力布局时,我有问题将箭头添加到曲线的末尾。有一个例子jsFiddle

我如何解决这个错误,箭头是有问题的,但是当图形移动时,不能准确地在行的末尾。

箭头看起来应该像这个例子:force layout with arrows SVG



var json = {
"nodes": [{
    "id": -1146034065,
        "name": "/",
        "group": 0
}, {
    "id": -990073683,
        "name": "/blog/",
        "group": 0
}, {
    "id": -1724280020,
        "name": "/menu/",
        "group": 0
}, {
    "id": 1176095248,
        "name": "/napojovy-listek/",
        "group": 0
}, {
    "id": -2085082741,
        "name": "/fotogalerie/",
        "group": 0
}, {
    "id": 883542796,
        "name": "/rezervace/",
        "group": 0
}, {
    "id": 369131020,
        "name": "/kontakt/",
        "group": 0
}, {
    "id": -1276353015,
        "name": "/en/",
        "group": 0
}, {
    "id": -1557747058,
        "name": "/o-nas/",
        "group": 404
}, {
    "id": 890427810,
        "name": "/en/about-us/",
        "group": 0
}, {
    "id": -978700858,
        "name": "/en/menu-2/",
        "group": 0
}, {
    "id": 1436673749,
        "name": "/en/napojovy-listek/",
        "group": 0
}, {
    "id": -489730654,
        "name": "/en/photograph/",
        "group": 0
}, {
    "id": -1461616187,
        "name": "/en/reservation/",
        "group": 0
}, {
    "id": 1520755615,
        "name": "/en/contact/",
        "group": 0
}, {
    "id": 37644686,
        "name": "/en//kontakt/",
        "group": 0
}, {
    "id": 1131720527,
        "name": "/en//o-nas/",
        "group": 404
}],
    "links": [{
    "source": -990073683,
        "target": -1146034065,
        "value": 1
},  {
    "source": -1461616187,
        "target": 883542796,
        "value": 1
}, {
    "source": 1520755615,
        "target": 369131020,
        "value": 1
}, {
    "source": 37644686,
        "target": -1276353015,
        "value": 1
}, {
    "source": 1131720527,
        "target": -1276353015,
        "value": 1
}, {
    "source": -1146034065,
        "target": -1146034065,
        "count": 1,
        "value": 1
}, {
    "source": -1146034065,
        "target": -990073683,
        "count": 1,
        "value": 1
}, {
    "source": -1146034065,
        "target": -1724280020,
        "count": 20,
        "value": 1
}, {
    "source": -1146034065,
        "target": 1176095248,
        "count": 3,
        "value": 1
}, {
    "source": -1146034065,
        "target": -2085082741,
        "count": 3,
        "value": 1
}, {
    "source": -1146034065,
        "target": 883542796,
        "count": 3,
        "value": 1
}, {
    "source": -1146034065,
        "target": 369131020,
        "count": 3,
        "value": 1
}, {
    "source": -1146034065,
        "target": -1276353015,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": -1146034065,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": -990073683,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": -1724280020,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": 1176095248,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": -2085082741,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": 883542796,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": 369131020,
        "count": 1,
        "value": 1
}, {
    "source": -1724280020,
        "target": -978700858,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": -1146034065,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": -990073683,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": -1724280020,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": 1176095248,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": -2085082741,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": 883542796,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": 369131020,
        "count": 1,
        "value": 1
}, {
    "source": 1176095248,
        "target": 1436673749,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": -1146034065,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": -990073683,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": -1724280020,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": 1176095248,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": -2085082741,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": 883542796,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": 369131020,
        "count": 1,
        "value": 1
}, {
    "source": -2085082741,
        "target": -489730654,
        "count": 1,
        "value": 1
},  {
    "source": 369131020,
        "target": -990073683,
        "count": 1,
        "value": 1
}, {
    "source": 369131020,
        "target": -1724280020,
        "count": 1,
        "value": 1
}, {
    "source": 369131020,
        "target": 1176095248,
        "count": 1,
        "value": 1
}, {
    "source": 369131020,
        "target": -2085082741,
        "count": 1,
        "value": 1
}, {
    "source": 369131020,
        "target": 883542796,
        "count": 1,
        "value": 1
}, {
    "source": 369131020,
        "target": 369131020,
        "count": 1,
        "value": 1
}, {
    "source": -978700858,
        "target": 1520755615,
        "count": 1,
        "value": 1
}, {
    "source": -978700858,
        "target": -1724280020,
        "count": 1,
        "value": 1
}

]
}

var app = {};

var width = 500,
  height = 500;

var svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height);

d3.select("body").append("use").attr("id", "use").attr("xlink:href", "#c1");

// Create a canvas element and set its size.
var canvas = d3.select('body').append('canvas')
  .attr('width', width + 'px')
  .attr('height', height + 'px')
  .node();


// Get the canvas context.
var context = canvas.getContext('2d');




var edges = [];
var fill = d3.scale.category10();

json.links.forEach(function(e) {
  // Get the source and target nodes
  var sourceNode = json.nodes.filter(function(n) {
      return n.id === e.source;
    })[0],
    targetNode = json.nodes.filter(function(n) {
      return n.id === e.target;
    })[0],
    count = e.count;

  // Add the edge to the array
  edges.push({
    source: sourceNode,
    target: targetNode,
    count: count,
    type: "a"
  });
});

var force = d3.layout.force()
  .linkDistance(320)
  .charge(0)
  .size([width, height])
  .nodes(json.nodes)
  .links(edges)
  .start();



var link = svg.append("svg:g").selectAll("link")
  .data(edges)
  .enter().append("svg:path")
  .attr("class", "link")
  .attr("stroke", "crimson")
  .attr("marker-end", "url(#noactive)")
  .style("stroke-width", function(d) {
    return Math.sqrt(d.count * 1, 5);
  });


// Přidáme k uzlu kontextové menu a zvýrazníme sousedy
var node = svg.selectAll("node")
  .data(json.nodes)
  .enter().append("g")
  .attr("class", "node")
  .style("fill", function(d) {
    return fill(d.group);
  })
  .call(force.drag).on("mouseover", fade(.1)).on("mouseout", fade(1))
  .on("click", function(d, i) {
    console.log("hi");
    d3.selectAll(".visibleGraph").remove();
    svg.selectAll('circle').transition().duration(300).attr("r", 10);
    svg.selectAll(".node").style("fill", function(d) {
      return fill(d.group);
    });
    d3.select(".menu").remove();






    var thisNode = d3.select(this);
    app.currentNode = thisNode;

    thisNode.moveToFront(); // přeskládám pořadí aby bylo menu nejvíce vepředu
    thisNode.attr('r', 25).style("fill", "lightcoral");
    thisNode.select('circle').transition().duration(300).attr("r", 28);

    fade(.1);

    var menuDataSet = [{
      size: 2,
      label: "Item 1",
      icon: "\uf082",
      graph: "wordCount",
    }, {
      size: 1,
      label: "Item 2",
      icon: "\uf081",
    }, {
      size: 65,
      label: "Item 3",
      icon: "\uf081",
    }, {
      size: 45,
      icon: "\uf081",
    }, {
      size: 50,
      icon: "\uf081",
    }];

    // Barvy menu
    var color = d3.scale.category20();
    var pie = d3.layout.pie()
      .sort(null)
      .value(function(d) {
        return Object.keys(menuDataSet).length;
      }); // zde je nutné zadat celkovou populaci - početz prvků v 

    // Menu
    var widthMenu = 180,
      heightMenu = 180,
      radiusMenu = Math.min(widthMenu, heightMenu) / 2;

    // Arc setting
    var arc = d3.svg.arc()
      .innerRadius(radiusMenu - 70)
      .outerRadius(radiusMenu - 25);

    // Graph space
    var svgMenu = thisNode.append("svg")
      .attr("width", widthMenu)
      .attr("height", heightMenu)
      .attr("class", "menu")
      .attr("x", -90)
      .attr("y", -90)
      .append("g")
      .attr("transform", "translate(" + widthMenu / 2 + "," + heightMenu / 2 + ")");


    // Prepare graph and load data
    var g = svgMenu.selectAll(".arc")
      .data(pie(menuDataSet))
      .enter().append("g")
      .attr("class", "arc");

    // Add colors
    var path = g.append("path")
      .attr("d", arc)
      .attr("fill", function(d) {
        return color(d.data.size);
      });

    // Add labels
    var asdfd = g.append("text")
      .attr("class", "fa-icon")
      .attr("pointer-events", "none") // zdeaktivni ikonku -> bude fungovat hover efekt pro i po najetí na ikonku
      .attr("transform", function(d) {
        return "translate(" + arc.centroid(d) + ")";
      })
      .attr("dy", ".35em")
      .style("text-anchor", "middle")
      .style("cursor", "pointer")
      .text(function(d) {
        return d.data.icon;
      });



    // Add hover action
    path.on("mouseenter", function(d, i) {

      app.currentMenuLabel = d.data.label;
      app.currentMenuItemGraph = d.data.graph;

      var thisPath = d3.select(this);
      thisPath.attr("fill", "blue")
        .attr("cursor", "pointer")
        .attr("class", "on")
        .on("click", function(d) {
          d3.event.stopPropagation();
          d3.selectAll(".visibleGraph").remove();

          // Graf 1
          if (app.currentMenuItemGraph == "wordCount") {
            console.log(app.currentMenuItemGraph);

            var thisNode = app.currentNode;

            console.log(thisNode);

            var dataset = {
              hddrives: [20301672448, 9408258048, 2147483648, 21474836480, 35622912, 32212254720],
            };

            var width = 460,
              height = 300,
              radius = Math.min(width, height) / 2;

            var color = d3.scale.ordinal()
              .range(["#2DA7E2"]);

            var pie = d3.layout.pie()
              .sort(null);

            var arc = d3.svg.arc()
              .innerRadius(radius - 100)
              .outerRadius(radius - 70);

            var svg = thisNode.append("svg")
              .attr("class", "visibleGraph")
              .attr("width", width)
              .attr("height", height)
              .attr("x", -210)
              .attr("y", -310)
              .append("g")
              .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

            svg.append("rect")
              .attr("width", 250)
              .attr("height", 180)
              .attr("class", "shadow")
              .attr("rx", 5)
              .attr("ry", 5)
              .attr("x", -120)
              .attr("y", -90)
              .attr("fill", "#E5E9E8");

            //Draw the Circle
            svg.append("circle")
              .attr("cx", 0)
              .attr("cy", 0)
              .attr("r", 65)
              .attr("fill", "#F1F1F1");

            console.log("test");
            var path = svg.selectAll("path")
              .data(pie(dataset.hddrives))
              .enter().append("path")
              .attr("class", "arc")
              .style("opacity", function(d, i) {
                return i == dataset.hddrives.length - 1 ? 0 : 1;
              })
              .attr("fill", function(d, i) {
                return color(i);
              })
              .attr("d", arc);
            svg.append("text")
              .attr("dy", "0em")
              .style("text-anchor", "middle")
              .attr("class", "inside")
              .text(function(d) {
                return '56%';
              });
            svg.append("text")
              .attr("dy", "1.5em")
              .style("text-anchor", "middle")
              .attr("class", "data")
              .text(function(d) {
                return '53GB / 123GB';
              });
          }
          // další grafy...



        });


    })

    path.on("mouseout", function(d) {
      d3.select(this)
        .attr("fill", function(d) {
          return color(d.data.size);
        })
        .attr("class", "off");
    });

  });





/*
  node.append("image")
      .attr("xlink:href", "https://github.com/favicon.ico")
      .attr("x", -8)
      .attr("y", -8)
      .attr("width", 16)
      .attr("height", 16);
*/
node.append("circle").attr("r", 10);

node.append("text")
  .attr("dx", 12)
  .attr("dy", ".35em")
  .text(function(d) {
    return d.name
  });


function draw_curve(Ax, Ay, Bx, By, M, context) {
  var dx = Bx - Ax,
    dy = By - Ay,
    dr = Math.sqrt(dx * dx + dy * dy);

  // side is either 1 or -1 depending on which side you want the curve to be on.
  // Find midpoint J
  var Jx = Ax + (Bx - Ax) / 2
  var Jy = Ay + (By - Ay) / 2

  // We need a and b to find theta, and we need to know the sign of each to make sure that the orientation is correct.
  var a = Bx - Ax
  var asign = (a < 0 ? -1 : 1)
  var b = By - Ay
  var bsign = (b < 0 ? -1 : 1)
  var theta = Math.atan(b / a)

  // Find the point that's perpendicular to J on side
  var costheta = asign * Math.cos(theta)
  var sintheta = asign * Math.sin(theta)

  // Find c and d
  var c = M * sintheta
  var d = M * costheta

  // Use c and d to find Kx and Ky
  var Kx = Jx - c
  var Ky = Jy + d
    // context.bezierCurveTo(Kx, Ky,Bx,By, Ax, Ax);
  context.quadraticCurveTo(Kx, Ky, Bx, By);

  // draw the ending arrowhead
  var endRadians = Math.atan((dx) / (dy));
  context.stroke();

  drawArrowhead(context, Bx, By, endRadians);
  // context.arc(Jx, Jy, dr, dr, 2 * Math.PI, true);

  /*
    return "M" + Ax + "," + Ay +
           "Q" + Kx + "," + Ky +
           " " + Bx + "," + By
   */
}

function drawArrowhead(ctx, x, y, radians) {
  ctx.save();
  ctx.beginPath();
  ctx.translate(x, y);
  ctx.rotate(radians);
  ctx.moveTo(0, 0);
  ctx.lineTo(5, 20);
  ctx.lineTo(-5, 20);
  ctx.closePath();
  ctx.restore();
  ctx.fill();
}



force.on("tick", function() {


  // Clear the complete figure.
  context.clearRect(0, 0, width, height);


  var x = 50;
  var y = 50;
  var radius = 50;
  var startAngle = (Math.PI / 180) * 30;
  var endAngle = (Math.PI / 180) * 90;
  var anticlockwise = false;

  context.beginPath();
  context.arc(x, y, radius, startAngle, endAngle, anticlockwise);
  context.stroke();
  context.closePath();

  edges.forEach(function(d) {
    // Draw a line from source to target.
    context.beginPath();
    //context.fillStyle = "red";
    //context.lineTo(d.source.x, d.target.y);

    context.moveTo(d.source.x, d.source.y);
    draw_curve(d.source.x, d.source.y, d.target.x, d.target.y, 30, context);
    // context.quadraticCurveTo(288, 0, 388, 150);
    // context.arcTo(d.source.x, d.source.y, d.target.x, d.target.y, 30);

  });

  json.nodes.forEach(function(d, i) {
    // Draws a complete arc for each node.
    context.beginPath();
    context.fillStyle = d.color;
    context.arc(d.x, d.y, 10, 0, 2 * Math.PI, true);
    context.fill();
  });


  /*

  //link.attr("d", linkArc);
  link.attr("d", function (d) {
      var dx = d.target.x - d.source.x,
          dy = d.target.y - d.source.y,
          dr = Math.sqrt(dx * dx + dy * dy),
          normX = dx / ((dr != 0) ? dr : 1),
          normY = dy / ((dr != 0) ? dr : 1),
          sourcePadding = d.left ? 50 : 5,
          targetPadding = d.right ? 50 : 5,
          sourceX = d.source.x + (sourcePadding * normX),
          sourceY = d.source.y + (sourcePadding * normY),
          targetX = d.target.x - (targetPadding * normX),
          targetY = d.target.y - (targetPadding * normY);

      console.log(sourceX + " " + d.source.x + " NORMX " + normX + " PADDING " + sourcePadding + " dr " + dr);

      //             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;

      return "M" + sourceX + "," + sourceY + "A" + dr + "," + dr + " 0 0,1 " + targetX + "," + targetY;
  });
    
  */

  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  });

  /*
    labels.attr("x", function (d) {
        return (d.source.x + d.target.x + 10) / 2;
    })
        .attr("y", function (d) {
        return (d.source.y + d.target.y + 10) / 2;
    })

*/
});


d3.selection.prototype.moveToFront = function() {
  return this.each(function() {
    this.parentNode.appendChild(this);
  });
};


function transform(d) {
  return "translate(" + d.x + "," + d.y + ")";
}



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;
}


var linkedByIndex = {};
edges.forEach(function(d) {
  linkedByIndex[d.source.index + "," + d.target.index] = 1;
});

function isConnected(a, b) {
  return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}

function tweenPie(b) {
  var i = d3.interpolate({
    startAngle: 1.1 * Math.PI,
    endAngle: 1.1 * Math.PI
  }, b);
  return function(t) {
    return arc(i(t));
  };
}

function fade(opacity) {
  return function(d) {

    // přidá popisky k hranám
    /*
    var labels = svg.selectAll('text')
        .data(edges)
        .enter().append('text')
        .attr("x", function (o) {
        return (o.source.y + o.target.y) / 2;
    })
        .attr("y", function (o) {
        return (o.source.x + o.target.x) / 2;
    })
        .attr("text-anchor", "middle")
        .text(function (o) {
        return o.count;
    });
    */

    node.transition().style("stroke-opacity", function(o) {
      thisOpacity = isConnected(d, o) ? 1 : opacity;
      this.setAttribute('fill-opacity', thisOpacity);
      return thisOpacity;
    });

    link.style("stroke-opacity", function(o) {

      return o.source === d || o.target === d ? 1 : opacity;
    }).style("stroke", function(o) {
      return o.source === d || o.target === d ? 1 : opacity;
    });

    link.attr("marker-end", function(o) {
      return o.source === d || o.target === d ? "url(#noactive)" : "url(#active)";
    });








  };
}



//---Insert-------
svg.append("defs").selectAll("marker")
  .data(["noactive", "active", "resolved"])
  .enter().append("marker")
  .attr("id", function(d) {
    return d;
  })
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 15)
  .attr("refY", -1.5)
  .attr("markerWidth", 6)
  .attr("markerHeight", 6)
  .attr("orient", "auto")
  .append("path")
  .attr("d", "M0,-5L10,0L0,5");
//---End Insert---
&#13;
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}
svg {
  position: absolute;
}
@font-face {
  font-family: FontAwesome;
  src: url(https://netdna.bootstrapcdn.com/font-awesome/2.0/font//fontawesome-webfont.eot?#iefix) format('eot'), url(https://netdna.bootstrapcdn.com/font-awesome/2.0/font//fontawesome-webfont.woff) format('woff'), url(https://netdna.bootstrapcdn.com/font-awesome/2.0/font//fontawesome-webfont.ttf) format('truetype'), url(https://netdna.bootstrapcdn.com/font-awesome/2.0/font//fontawesome-webfont.svg#FontAwesome) format('svg');
  font-weight: 400;
  font-style: normal;
}
.fa-icon {
  font-family: 'FontAwesome';
  font-size: 20px;
  fill: #fff !important;
}
.shadow {
  -webkit-svg-shadow: 0 0 7px #53BE12;
  -webkit-filter: drop-shadow(-5px -5px 5px #000);
  filter: drop-shadow(-5px -5px 5px #000);
  /* Same syntax as box-shadow */
}
.link {
  fill: none;
  stroke: #DCDCDC;
  stroke-width: 1px !important;
}
marker#active {
  fill-opacity: 0.1;
  stroke-opacity: 0.1;
}
.arc {
  cursor: pointer;
}
text {
  font: 10px sans-serif;
}
form {
  position: absolute;
  right: 10px;
  top: 10px;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
&#13;
&#13;

感谢您的帮助!

0 个答案:

没有答案