当我使用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;
感谢您的帮助!