var config = [];
var graph = {
nodes: [
{ name: "A" },
{ name: "B" },
{ name: "C" },
{ name: "D" },
{ name: "Dummy1" },
{ name: "Dummy2" },
{ name: "Dummy3" },
{ name: "Dummy4" }
],
links: [
{ source: "A", target: "B", linkname: "A0" },
{ source: "A", target: "C", linkname: "A0" },
{ source: "A", target: "D", linkname: "A1" },
{ source: "Dummy1", target: "A", linkname: "input" },
{ source: "B", target: "Dummy2", linkname: "B" },
{ source: "C", target: "Dummy3", linkname: "C" },
{ source: "D", target: "Dummy4", linkname: "D" }
]
};
var optArray = [];
labelAnchors = [];
labelAnchorLinks = [];
highlightNode_button = d3.select("#highlightNode");
highlightNode_button.on("click", highlightNode);
shrinkNode_button = d3.select("#shrinkNode");
shrinkNode_button.on("click", minimizeNode);
for (var i = 0; i < graph.nodes.length - 1; i++) {
optArray.push(graph.nodes[i].name);
}
optArray = optArray.sort();
$(function() {
$("#targetNode").autocomplete({
source: optArray
});
});
//used to find max thickness of all the nodes, which is used for normalizing later on.
var max_thickness = d3.max(graph.links, function(d) {
return d.thickness;
});
//used to find min thickness of all the nodes, which is used for normalizing later on.
var min_thickness = d3.min(graph.links, function(d) {
return d.thickness;
});
var svg1 = d3.select("svg");
var width = +screen.width;
var height = +screen.height - 500;
svg1.attr("width", width).attr("height", height);
var zoom = d3.zoom().on("zoom", zoomed);
function zoomed() {
svg.attr("transform", d3.event.transform);
}
var svg = svg1
// .call(
// zoom.on("zoom", function() {
// svg.attr("transform", d3.event.transform);
// })
// )
.call(zoom)
.on("dblclick.zoom", null)
.append("g");
// Defining the gradient
//used for coloring the nodes
var gradient = svg
.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
// Define the gradient colors
gradient
.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#b721ff")
.attr("stop-opacity", 1);
gradient
.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#21d4fd")
.attr("stop-opacity", 1);
var linkText = svg
.selectAll(".gLink")
.data(graph.links)
.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("x", function(d) {
if (d.target.x > d.source.x) {
return d.source.x + (d.target.x - d.source.x) / 2;
} else {
return d.target.x + (d.source.x - d.target.x) / 2;
}
})
.attr("y", function(d) {
if (d.target.y > d.source.y) {
return d.source.y + (d.target.y - d.source.y) / 2;
} else {
return d.target.y + (d.source.y - d.target.y) / 2;
}
})
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("dy", ".35em")
.text(function(d) {
return d.linkname;
});
var dragDrop = d3
.drag()
.on("start", node => {
node.fx = node.x;
node.fy = node.y;
})
.on("drag", node => {
simulation.alphaTarget(1).restart();
node.fx = d3.event.x;
node.fy = d3.event.y;
})
.on("end", node => {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
node.fx = null;
node.fy = null;
});
var linkForce = d3
.forceLink(graph.links)
.id(function(d) {
return d.name;
})
.distance(50);
//.strength(0.5) to specify the pulling strength from each link
// strength from each node
// Saving a reference to node attribute
var nodeForce = d3.forceManyBody().strength(-30);
var simulation = d3
.forceSimulation(graph.nodes)
.force("links", linkForce)
.force("charge", nodeForce)
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
var div = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg
.append("defs")
.append("marker")
.attrs({
id: "arrowhead",
viewBox: "-0 -5 10 10",
refX: 100,
refY: 0,
orient: "auto-start-reverse",
markerWidth: 3,
markerHeight: 3
})
.append("svg:path")
.attr("d", "M 0,-5 L 10 ,0 L 0,5")
.attr("fill", "black");
{
{
/* .call(
d3.zoom().on("zoom", function() {
console.log(d3.event.transform);
svg.attr("transform", d3.event.transform);
})
); */
}
}
var link = svg
.append("g")
.selectAll("line_class.line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", function(d) {
return Math.max(
((d.thickness - min_thickness) / (max_thickness - min_thickness + 0.01)) *
5,
3
);
})
.style("stroke", "pink")
.text("text", function(d) {
return d.linkname;
})
.on("mouseover", function(d) {
div
.transition()
.duration(200)
.style("opacity", 0.9);
div
.html("Name: " + d.linkname)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY - 28 + "px")
.style("width", "auto")
.style("height", "auto");
})
.on("mouseout", function(d) {
div
.transition()
.duration(500)
.style("opacity", 0);
})
.attr("marker-start", "url(#arrowhead)");
// .attr("marker-start", "url(#arrowhead)")
// .attr("marker-mid", "url(#arrowhead)")
// .attr("marker-end", "url(#arrowhead)");
// var line = link
var edgepaths = svg
.selectAll(".edgepath")
.data(graph.links)
.enter()
.append("path")
.attr("d", function(d) {
return (
"M " +
d.source.x +
" " +
d.source.y +
" L " +
d.target.x +
" " +
d.target.y
);
})
.attr("class", "edgepath")
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.attr("fill", "blue")
.attr("stroke", "red")
.attr("id", function(d, i) {
return "edgepath" + i;
})
.style("pointer-events", "none");
var radius = 4;
var node_data_array = [];
var node_name = "";
var node = svg
.append("g")
.selectAll("circle_class.circle")
.data(graph.nodes)
.enter()
.append("rect")
.attr("width", 40)
.attr("height", 20)
.attr("fill", "url(#gradient)")
.style("transform", "translate(-20px,-10px)")
.attr("stroke", "purple")
.attr("id", function(d) {
node_name = d.name;
return d.name;
})
.on("dblclick", connectedNodes);
var textElements_nodes = svg
.append("g")
.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.text(function(d) {
return d.name;
})
.attr("font-size", 15)
.attr("dx", 15)
.attr("dy", -5);
node.call(dragDrop);
link.call(updateState1);
var path = document.querySelector("path"),
totalLength = path.getTotalLength(),
group = totalLength / 20,
start;
var arrowheads = d3
.select("svg")
.selectAll("use")
.data(
d3.range(20).map(function(d) {
return d * group + 50;
})
)
.enter()
.append("use")
.attr("xlink:href", "#arrowhead");
path.style.strokeDasharray = "50," + (group - 50);
requestAnimationFrame(update);
function update(t) {
if (!start) {
start = t;
}
var offset = (-group * ((t - start) % 900)) / 900;
path.style.strokeDashoffset = offset;
arrowheads.attr("transform", function(d) {
var l = d - offset;
if (l < 0) {
l = totalLength + l;
} else if (l > totalLength) {
l -= totalLength;
}
var p = pointAtLength(l);
return "translate(" + p + ") rotate( " + angleAtLength(l) + ")";
});
//requestAnimationFrame(update);
}
function pointAtLength(l) {
var xy = path.getPointAtLength(l);
return [xy.x, xy.y];
}
// Approximate tangent
function angleAtLength(l) {
var a = pointAtLength(Math.max(l - 0.01, 0)), // this could be slightly negative
b = pointAtLength(l + 0.01); // browsers cap at total length
return (Math.atan2(b[1] - a[1], b[0] - a[0]) * 180) / Math.PI;
}
// link.attr("marker-end", "url(#end)");
function updateState1() {
link.each(function(d) {
var colors = ["red", "green", "blue"];
var num = 0;
if (d.source.name.startsWith("Dummy")) {
num = 1;
console.log("Inside 1");
console.log(
"Source is ",
d.source.name,
" and target is ",
d.target.name
);
} else if (d.target.name.startsWith("Dummy")) {
num = 2;
console.log("Inside 2");
console.log(
"Source is ",
d.source.name,
" and target is ",
d.target.name
);
} else {
num = 0;
for (i = 0; i < graph.input_nodes.length; i++) {
if (graph.input_nodes[i].name == d.source.name) {
num = 1;
}
}
}
d3.select(this).style("stroke", function(d) {
return colors[num];
});
{
{
/* d3.select(this).attr("marker-end", "url(#arrowhead)"); */
}
}
{
{
/* d3.select(this).attr("marker-mid", "url(#arrowhead)"); */
}
}
// .attr("marker-end", "url(#arrowhead)");
});
}
function pointOnRect(x, y, minX, minY, maxX, maxY, validate) {
//assert minX <= maxX;
//assert minY <= maxY;
if (validate && minX < x && x < maxX && minY < y && y < maxY)
throw "Point " +
[x, y] +
"cannot be inside " +
"the rectangle: " +
[minX, minY] +
" - " +
[maxX, maxY] +
".";
var midX = (minX + maxX) / 2;
var midY = (minY + maxY) / 2;
// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
var m = (midY - y) / (midX - x);
if (x <= midX) {
// check "left" side
var minXy = m * (minX - x) + y;
if (minY <= minXy && minXy <= maxY) return { x: minX, y: minXy };
}
if (x >= midX) {
// check "right" side
var maxXy = m * (maxX - x) + y;
if (minY <= maxXy && maxXy <= maxY) return { x: maxX, y: maxXy };
}
if (y <= midY) {
// check "top" side
var minYx = (minY - y) / m + x;
if (minX <= minYx && minYx <= maxX) return { x: minYx, y: minY };
}
if (y >= midY) {
// check "bottom" side
var maxYx = (maxY - y) / m + x;
if (minX <= maxYx && maxYx <= maxX) return { x: maxYx, y: maxY };
}
// edge case when finding midpoint intersection: m = 0/0 = NaN
if (x === midX && y === midY) return { x: x, y: y };
// Should never happen :) If it does, please tell me!
throw "Cannot find intersection for " +
[x, y] +
" inside rectangle " +
[minX, minY] +
" - " +
[maxX, maxY] +
".";
}
var label_toggle = 0;
function show_hide_node_labels() {
if (label_toggle) {
textElements_nodes.style("visibility", "visible");
} else {
textElements_nodes.style("visibility", "hidden");
}
label_toggle = !label_toggle;
}
var toggle = 0;
var linkedByIndex = {};
for (i = 0; i < graph.nodes.length; i++) {
linkedByIndex[i + "," + i] = 1;
}
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
//DAT GUI for controls
var gui = new dat.GUI({ width: 300 });
config = {
linkStrength: 1,
linkDistance: 180,
nodeStrength: -30,
Width: 40,
Height: 20,
restart: reset,
showHideNodeLabels: show_hide_node_labels
};
var linkDistanceChanger = gui
.add(config, "linkDistance", 0, 400)
.step(1)
.name("Link Distance");
linkDistanceChanger.onChange(function(value) {
linkForce.distance(value);
simulation.alpha(1).restart();
});
var linkStrengthChanger = gui
.add(config, "linkStrength", 0, 1)
.name("Link Strength");
linkStrengthChanger.onChange(function(value) {
linkForce.strength(value);
simulation.alpha(1).restart();
});
var nodeStrengthChanger = gui
.add(config, "nodeStrength", -500, -1)
.step(1)
.name("Node Strength");
nodeStrengthChanger.onChange(function(value) {
nodeForce.strength(value);
simulation.alpha(1).restart();
});
var widthChanger = gui
.add(config, "Width", 4, 100)
.step(1)
.name("Node Width");
widthChanger.onChange(function(value) {
node.attr("width", value);
og_width = value;
// d3.select("#arrowhead").attrs({
// markerWidth: value * 0.4,
// markerHeight: value * 0.4
// });
simulation.alpha(1).restart();
});
var heightChanger = gui
.add(config, "Height", 2, 80)
.step(1)
.name("Node Height");
heightChanger.onChange(function(value) {
node.attr("height", value);
og_height = value;
// d3.select("#arrowhead").attrs({
// markerWidth: value * 0.4,
// markerHeight: value * 0.4
// });
simulation.alpha(1).restart();
});
gui.add(config, "showHideNodeLabels").name("Show/Hide Node Labels");
gui.add(config, "restart").name("Restart");
for (i = 0; i < gui.__ul.childNodes.length; i++) {
gui.__ul.childNodes[i].classList += " longtext";
}
function reset() {
window.location.reload();
}
//This function looks up whether a pair are neighbours
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function connectedNodes() {
if (toggle == 0) {
//Reduce the opacity of all but the neighbouring nodes
d = d3.select(this).node().__data__;
node.style("opacity", function(o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
link.style("opacity", function(o) {
return (d.index == o.source.index) | (d.index == o.target.index)
? 1
: 0.1;
});
textElements_nodes.style("opacity", function(o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
toggle = 1;
} else {
//Put them back to opacity=1
node.style("opacity", 1);
link.style("opacity", 1);
textElements_nodes.style("opacity", 1);
toggle = 0;
}
}
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
})
.attr("d", function(d) {
var inter = pointOnRect(
d.source.x,
d.source.y,
d.target.x - 20,
d.target.y - 20,
d.target.x + 40 - 20,
d.target.y + 20 - 20
);
return (
"M" + d.source.x + "," + d.source.y + "L" + inter.x + "," + inter.y
);
});
node
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
textElements_nodes.attr("x", node => node.x).attr("y", node => node.y);
edgepaths.attr("d", function(d) {
var path =
"M " +
d.source.x +
" " +
d.source.y +
" L " +
d.target.x +
" " +
d.target.y;
return path;
});
var ticking = false;
}
var pulse = false;
function pulsed(rect) {
(function repeat() {
if (pulse) {
rect
.transition()
.duration(200)
.attr("stroke-width", 0)
.attr("stroke-opacity", 0)
.attr("width", config.Width)
.attr("height", config.Height)
.transition()
.duration(200)
.attr("stroke-width", 0)
.attr("stroke-opacity", 0.5)
.attr("width", config.Width * 3)
.attr("height", config.Height * 3)
.transition()
.duration(400)
.attr("stroke-width", 65)
.attr("stroke-opacity", 0)
.attr("width", config.Width)
.attr("height", config.Height)
.ease(d3.easeSin)
.on("end", repeat);
} else {
ticking = false;
rect
.attr("width", config.Width)
.attr("height", config.Height)
.attr("fill", "url(#gradient)")
.attr("stroke", "purple");
}
})();
}
function highlightNode() {
var userInput = document.getElementById("targetNode");
var temp = userInput.value;
var userInputRefined = temp.replace(/[/]/g, "\\/");
// make userInput work with "/" as they are considered special characters
// the char "/" is escaped with escape characters.
theNode = d3.select("#" + userInputRefined);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
pulse = true;
if (pulse) {
pulsed(theNode);
}
scalingFactor = 0.5;
// Create a zoom transform from d3.zoomIdentity
var transform = d3.zoomIdentity
.translate(
screen.width / 2 - scalingFactor * theNode.attr("x"),
screen.height / 4 - scalingFactor * theNode.attr("y")
)
.scale(scalingFactor);
// Apply the zoom and trigger a zoom event:
svg1.call(zoom.transform, transform);
}
function minimizeNode() {
var userInput = document.getElementById("targetNode");
var temp = userInput.value;
var userInputRefined = temp.replace(/[/]/g, "\\/");
// make userInput work with "/" as they are considered special characters
// the char "/" is escaped with escape characters.
theNode = d3.select("#" + userInputRefined);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
pulse = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<title>Force Layout Example 9</title>
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.longtext {
line-height: 13px;
height: 40px;
font-size: 120%;
}
rect.zoom-panel {
cursor: move;
fill: #fff;
pointer-events: all;
}
.bar {
fill: rgb(70, 180, 70);
}
.graph-svg-component {
background-color: green;
}
path {
fill: none;
stroke: #d3008c;
stroke-width: 2px;
}
#arrowhead {
fill: #d3008c;
stroke: none;
}
</style>
</head>
<body>
<svg></svg>
<div id="content"></div>
<script src="http://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-drag.v1.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"
></script>
<link
href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css"
rel="stylesheet"
type="text/css"
/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
</body>
</html>
Я думаю, что это то, что вы хотите, этот фрагмент кода на основе этого bl.ocks.org фрагмента и он должен быть анимированным если я удалил вызов requestAnimationFrame из функции обновления.
относительно вашей первоначальной проблемы с узлами, скрывающими стрелку, вы можете использовать refX и refY атрибут <marker>
для изменения его смещения.
var path = document.querySelector("path"),
totalLength = path.getTotalLength(),
group = totalLength / 20,
start;
var arrowheads = d3.select("svg").selectAll("use")
.data(d3.range(20).map(function(d){ return d * group + 50; }))
.enter()
.append("use")
.attr("xlink:href", "#arrowhead");
path.style.strokeDasharray = "50," + (group - 50);
requestAnimationFrame(update);
function update(t) {
if (!start) {
start = t;
}
var offset = -group * ((t - start) % 900) / 900;
path.style.strokeDashoffset = offset;
arrowheads.attr("transform",function(d){
var l = d - offset;
if (l < 0) {
l = totalLength + l;
} else if (l > totalLength) {
l -= totalLength;
}
var p = pointAtLength(l);
return "translate(" + p + ") rotate( " + angleAtLength(l) + ")";
});
//requestAnimationFrame(update);
}
function pointAtLength(l) {
var xy = path.getPointAtLength(l);
return [xy.x, xy.y];
}
// Approximate tangent
function angleAtLength(l) {
var a = pointAtLength(Math.max(l - 0.01,0)), // this could be slightly negative
b = pointAtLength(l + 0.01); // browsers cap at total length
return Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI;
}
path {
fill: none;
stroke: #d3008c;
stroke-width: 2px;
}
#arrowhead {
fill: #d3008c;
stroke: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="960" height="500">
<path d="M636.5,315c-0.4-18.7,1.9-27.9-5.3-35.9
c-22.7-25-107.3-2.8-118.3,35.9c-7,24.4,20.6,37.2,16,71c-4,29.6-30.8,60.7-56.5,61.1c-30.8,0.4-32.9-43.8-81.7-70.2
c-50.9-27.6-110.1-12.9-125.2-9.2c-66.1,16.4-82.2,56.9-109.2,47.3c-38-13.6-55.9-112.1-19.8-143.5c39-34,121.2,27.7,148.1-3.8
c18-21.1,3.1-74.3-25.2-105.3c-31.1-34.1-70.1-32.4-105.3-76.3c-8.2-10.2-16.9-23.8-15.3-39.7c1.2-11.4,7.5-23.3,15.3-29
c33.8-25,101.6,62.6,193.1,59.5c40.1-1.3,38.7-18.5,99.2-38.9c126.2-42.6,242.4-4.9,297.7,13c54.7,17.7,105.4,35,129.8,82.4
c13,25.3,22.9,67.7,4.6,87c-11.6,12.3-25.1,5.1-46.6,20.6c-2.8,2-28.9,21.4-32.1,49.6c-3.1,27.4,18.7,35,29,70.2
c8.8,30.1,8.5,77.8-18.3,99.2c-32.3,25.8-87,0.6-100-5.3c-69.6-32-67.2-88.4-73.3-109.2z"/>
<defs>
<path id="arrowhead" d="M7,0 L-7,-5 L-7,5 Z" />
</defs>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>