#javascript #d3.js
#javascript #d3.js
Вопрос:
У меня есть график макета, ориентированный на силу d3, который похож на график, ориентированный на силу, с перетаскиванием / масштабированием / панорамированием / центрированием / изменением размера / метками / фигурами / фильтром / подсветкой. У меня возникли некоторые проблемы с добавлением к нему сворачиваемой функциональности, которую я намерен вызывать из меню, когда вы щелкаете правой кнопкой мыши по узлу. Есть идеи? Ниже, пожалуйста, найдите мой измененный код.
<!DOCTYPE html>
<meta charset="utf-8">
<title>
Collapsible Force Layout Graph using non-tree data with a d3-context-menu
</title>
<head>
<script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link href="css/style.css" rel="stylesheet" />
<script src="js_script/d3-context-menu.js"></script>
</head>
<body>
<script language="JavaScript">
var width = 1300,
height = 700;
var h = 2,
w = 4;
var focus_node = null, highlight_node = null;
var focus_node = null, highlight_node = null;
var text_center = false;
var outline = false;
var min_score = 0;
var max_score = 1;
var color = d3.scale.category20();
var highlight_color = "#666";
var highlight_trans = 0.1;
var size = d3.scale.pow().exponent(1)
.domain([1,100])
.range([8,24]);
var force = d3.layout.force()
.size([width, height])
.charge(-400)
.linkDistance(40)
.size([w,h]);
var drag = force.drag()
.on("dragstart", dragstart);
var default_node_color = "#ccc";
var default_link_color = "#666";
var nominal_base_node_size = 8;
var nominal_text_size = 10;
var max_text_size = 24;
var nominal_stroke = 1.6;
var max_stroke = 1.6*7;
var max_base_node_size = 36;
var min_zoom = 0.1;
var max_zoom = 7;
var menu = [
{
title: 'Collapes Nodes',
action: function(elm, d, i) {
if (!d3.event.defaultPrevented) {
//check if link is from this node, and if so, collapse
root.links.forEach(function(l) {
if(l.source.id == d.id) {
if(d.collapsed){
l.target.collapsing--;
} else {
l.target.collapsing ;
}
}
});
d.collapsed = !d.collapsed;
}
update();
}
},
{
title: 'Expand Nodes',
action: function(elm, d, i) {
if (!d3.event.defaultPrevented) {
//check if link is from this node, and if so, collapse
root.links.forEach(function(l) {
if(l.source.id == d.id) {
if(d.collapsed){
l.target.collapsing--;
} else {
l.target.collapsing ;
}
}
});
d.collapsed = !d.collapsed;
}
update();
}
},
{
title: 'Release Node',
action: function(elm, d, i) {
d3.select(this).classed("fixed", d.fixed = false);
}
}
]
var svg = d3.select("body").append("svg");
var zoom = d3.behavior.zoom().scaleExtent([min_zoom,max_zoom])
var g = svg.append("g");
svg.style("cursor","move");
d3.json("data/risk_final-2.json", function(error, graph) {
root = graph;
//Give nodes ids and initialize variables
for(var i=0; i<root.nodes.length; i ) {
var node = root.nodes[i];
node.id = i;
node.collapsing = 0;
node.collapsed = false;
}
//Give links ids and initialize variables
for(var i=0; i<root.links.length; i ) {
var link = root.links[i];
link.source = root.nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = root.nodes[link.target] || (nodes[link.target] = {name: link.target});
link.id = i;
}
// update();
force
.nodes(graph.nodes)
.links(graph.links)
.start();
//Added markers to indicate that this is a directed graph
svg.append("defs").selectAll("marker")
.data(["arrow"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 2)
.attr("markerHeight", 2)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var link = g.selectAll(".link")
.data(graph.links)
// .data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("stroke-opacity", function(d) { if(d.label == 'is a') { return '0.8';} else{ return '0.2'; }; })
.style("stroke", function(d) { if(d.color !== null) { return d.color;}; })
.style("stroke-width", function(d) { return d.value; })
.style("stroke", function(d) {
if (isNumber(d.group) amp;amp; d.group>=0) return color(d.group);
else return default_link_color; })
.html(function(d) {
return " Link : " d.source.name " -> " d.target.name;
})
// action to take on mouseover over edges
.on("mouseover", function(d, i){d3.select(this)
.style("stroke", "#666")
.style("stroke-width", function(d) { return d.value*2;})
.attr("stroke-opacity", "1.0");})
// action to take on mouseout over edges
.on("mouseout", function(d, i){d3.select(this)
.style("stroke", function(d) { if(d.color !== null) { return d.color;}; })
.style("stroke-width", function(d) { return d.value; })
.attr("stroke-opacity", function(d) { if(d.label == 'is a') { return '0.8';} else { return '0.2'; };}) })
// Assign Id's to your path/Link
.attr("class", function(d) { return "link " d.type; })
.attr("id",function(d,i) { return "linkId_" i; })
// Displays the markers to indicate the link direction
.attr("marker-end", "url(#arrow)")
.call(force.drag);
//Use SVG textPath element for associating labels with above links
var linktext = svg.append("svg:g").selectAll("g.linklabelholder").data(force.links());
linktext.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("x", "50")
.attr("y", "-20")
.attr("text-anchor", "start")
.style("fill","#000")
.append("textPath")
.attr("xlink:href",function(d,i) { return "#linkId_" i;})
.text(function(d) {
return d.value;
});
//Show tool-tip on links
link.append("title")
.html(function(d) {
return " Link : " d.source.name " -> " d.target.name "<br/>" "Link Value : " d.value;
})
var node = g.selectAll("circle.node")
.data(graph.nodes)
//.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.attr("r", function(d) { return d.size; })
.call(force.drag)
.on('contextmenu', d3.contextMenu(menu))
node.on("dblclick.zoom", function(d) { d3.event.stopPropagation();
var dcx = (window.innerWidth/2-d.x*zoom.scale());
var dcy = (window.innerHeight/2-d.y*zoom.scale());
zoom.translate([dcx,dcy]);
g.attr("transform", "translate(" dcx "," dcy ")scale(" zoom.scale() ")");
});
//add the tooltip
node.append("title")
// line break ->.html
.html(function(d) {return "Name : " d.name "<br/>" "Group : " d.group "<br/>" "Type : " d.type "<br/>" "Size : " d.size;});
var tocolor = "fill";
var towhite = "stroke";
if (outline) {
tocolor = "stroke"
towhite = "fill"
}
var circle = node.append("path")
.attr("d", d3.svg.symbol()
.size(function(d) { return Math.PI*Math.pow(size(d.size)||nominal_base_node_size,2); })
.type(function(d) { return d.type; }))
.call(drag)
.style(tocolor, function(d) {
if (isNumber(d.group) amp;amp; d.group>=0) return color(d.group);
else return default_node_color; })
.style("stroke-width", nominal_stroke)
.style(towhite, "#ccc");
var text = g.selectAll(".text")
.data(graph.nodes)
// .data(force.nodes())
.enter().append("text")
.attr("dy", ".35em")
.style("font-size", nominal_text_size "px")
if (text_center)
text.text(function(d) { return d.name; })
.style("text-anchor", "middle");
else
text.attr("dx", function(d) {return (size(d.size)||nominal_base_node_size);})
.text(function(d) { return 'u2002' d.name; });
node.on("mouseover", function(d) {
set_highlight(d);
//highlight 1st-order links/edges
link.classed("link-active", function(o) {
return o.source === d || o.target === d ? true : false;
});
})
.on("mousedown", function(d) { d3.event.stopPropagation();
focus_node = d;
set_focus(d)
if (highlight_node === null) set_highlight(d)
} ).on("mouseout", function(d) {
exit_highlight();
// Hides the highlight 1st-order links/edges
link.classed("link-active", function(o) {
return false;
});
});
d3.select(window).on("mouseup",
function() {
if (focus_node!==null)
{
focus_node = null;
if (highlight_trans<1)
{
circle.style("opacity", 1);
text.style("opacity", 1);
link.style("opacity", 1);
}
}
if (highlight_node === null) exit_highlight();
});
function exit_highlight()
{
highlight_node = null;
if (focus_node===null)
{
svg.style("cursor","move");
if (highlight_color!="#ccc")
{
circle.style(towhite, "#ccc");
text.style("font-weight", "normal");
link.style("stroke", function(o) {return (isNumber(o.group) amp;amp; o.group>=0)?color(o.group):default_link_color});
}
}
}
function set_focus(d)
{
if (highlight_trans<1) {
circle.style("opacity", function(o) {
return isConnected(d, o) ? 1 : highlight_trans;
});
text.style("opacity", function(o) {
return isConnected(d, o) ? 1 : highlight_trans;
});
link.style("opacity", function(o) {
return o.source.index == d.index || o.target.index == d.index ? 1 : highlight_trans;
});
}
}
function set_highlight(d)
{
svg.style("cursor","pointer");
if (focus_node!==null) d = focus_node;
highlight_node = d;
if (highlight_color!="#ccc")
{
circle.style(towhite, function(o) {
return isConnected(d, o) ? highlight_color : "#ccc";});
text.style("font-weight", function(o) {
return isConnected(d, o) ? "bold" : "normal";});
link.style("stroke", function(o) {
return o.source.index == d.index || o.target.index == d.index ? highlight_color : ((isNumber(o.group) amp;amp; o.group>=0)?color(o.group):default_link_color);
});
}
}
zoom.on("zoom", function() {
var stroke = nominal_stroke;
if (nominal_stroke*zoom.scale()>max_stroke) stroke = max_stroke/zoom.scale();
// link/edge width adjust based on zoom-in and zoom-out
link.style("stroke", function(d) { if(d.color !== null) { return d.color;}; })
circle.style("stroke-width",stroke);
var base_radius = nominal_base_node_size;
if (nominal_base_node_size*zoom.scale()>max_base_node_size) base_radius = max_base_node_size/zoom.scale();
circle.attr("d", d3.svg.symbol()
.size(function(d) { return Math.PI*Math.pow(size(d.size)*base_radius/nominal_base_node_size||base_radius,2); })
.type(function(d) { return d.type; }))
if (!text_center) text.attr("dx", function(d) { return (size(d.size)*base_radius/nominal_base_node_size||base_radius); });
var text_size = nominal_text_size;
if (nominal_text_size*zoom.scale()>max_text_size) text_size = max_text_size/zoom.scale();
text.style("font-size",text_size "px");
g.attr("transform", "translate(" d3.event.translate ")scale(" d3.event.scale ")");
});
svg.call(zoom);
resize();
d3.select(window).on("resize", resize).on("keydown", keydown);
force.on("tick", function() {
node.attr("transform", function(d) { return "translate(" d.x "," d.y ")"; });
text.attr("transform", function(d) { return "translate(" d.x "," d.y ")"; });
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; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source "," d.target] = true;
});
function isConnected(a, b) {
return linkedByIndex[a.index "," b.index] || linkedByIndex[b.index "," a.index] || a.index == b.index;
}
function hasConnections(a) {
for (var property in linkedByIndex) {
s = property.split(",");
if ((s[0] == a.index || s[1] == a.index) amp;amp; linkedByIndex[property]) return true;
}
return false;
}
function resize() {
var width = window.innerWidth, height = window.innerHeight;
svg.attr("width", width).attr("height", height);
force.size([force.size()[0] (width-w)/zoom.scale(),force.size()[1] (height-h)/zoom.scale()]).resume();
w = width;
h = height;
}
function keydown() {
if (d3.event.keyCode==32) { force.stop();}
else if (d3.event.keyCode>=48 amp;amp; d3.event.keyCode<=90 amp;amp; !d3.event.ctrlKey amp;amp; !d3.event.altKey amp;amp; !d3.event.metaKey)
{
switch (String.fromCharCode(d3.event.keyCode)) {
case "C": keyc = !keyc; break;
case "S": keys = !keys; break;
case "T": keyt = !keyt; break;
case "R": keyr = !keyr; break;
case "X": keyx = !keyx; break;
case "D": keyd = !keyd; break;
case "L": keyl = !keyl; break;
case "M": keym = !keym; break;
case "H": keyh = !keyh; break;
case "1": key1 = !key1; break;
case "2": key2 = !key2; break;
case "3": key3 = !key3; break;
case "0": key0 = !key0; break;
}
link.style("display", function(d) {
var flag = vis_by_type(d.source.type)amp;amp;vis_by_type(d.target.type)amp;amp;vis_by_node_group(d.source.group)amp;amp;vis_by_node_group(d.target.group)amp;amp;vis_by_link_group(d.group);
linkedByIndex[d.source.index "," d.target.index] = flag;
return flag?"inline":"none";});
node.style("display", function(d) {
return (key0||hasConnections(d))amp;amp;vis_by_type(d.type)amp;amp;vis_by_node_group(d.group)?"inline":"none";});
text.style("display", function(d) {
return (key0||hasConnections(d))amp;amp;vis_by_type(d.type)amp;amp;vis_by_node_group(d.group)?"inline":"none";});
if (highlight_node !== null)
{
if ((key0||hasConnections(highlight_node))amp;amp;vis_by_type(highlight_node.type)amp;amp;vis_by_node_group(highlight_node.group)) {
if (focus_node!==null) set_focus(focus_node);
set_highlight(highlight_node);
}
else {exit_highlight();}
}
}
}
});
function vis_by_node_group(group)
{
if (isNumber(group))
{
if (group>=0.666) return keyh;
else if (group>=0.333) return keym;
else if (group>=0) return keyl;
}
return true;
}
function vis_by_link_group(group)
{
if (isNumber(group))
{
if (group>=0.666) return key3;
else if (group>=0.333) return key2;
else if (group>=0) return key1;
}
return true;
}
function isNumber(n) {
return !isNaN(parseFloat(n)) amp;amp; isFinite(n);
}
//Fixed property of the dragged node is set to true.
//This prevents the force layout from subsequently changing the position of the node (due to forces)
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
</script>
</body>
</html>
Данные json аналогичны приведенной ниже структуре.
{
"nodes": [{
"name": "WesternAustralia",
"group": "0.8",
"size": "7",
"type": "circle"
}, {
"name": "NewGuinea",
"group": "0.2",
"size": "7",
"type": "circle"
}, {
"name": "EasternAustralia",
"group": "0.8",
"size": "17",
"type": "circle"
}, {
"name": "Indonesia",
"group": "0.8",
"size": "16",
"type": "circle"
}, {
"name": "NorthernEurope",
"group": "1",
"size": "12",
"type": "square"
}, {
"name": "SouthernEurope",
"group": "0.8",
"size": "9",
"type": "square"
}, {
"name": "WesternEurope",
"group": "0.7",
"size": "8",
"type": "square"
}, {
"name": "East_Afrika",
"group": "0",
"size": "19",
"type": "square"
}, {
"name": "Egypt",
"group": "0.4",
"size": "5",
"type": "square"
}, {
"name": "Madagasca",
"group": "0.2",
"size": "3",
"type": "circle"
}, {
"name": "Congo",
"group": "0.3",
"size": "4",
"type": "square"
}, {
"name": "South_Afrika",
"group": "0.4",
"size": "5",
"type": "square"
}, {
"name": "North_Afrika",
"group": "1",
"size": "12",
"type": "square"
}, {
"name": "brazil",
"group": "0.7",
"size": "7",
"type": "square"
}, {
"name": "argentina",
"group": "0.6",
"size": "6",
"type": "square"
}, {
"name": "peru",
"group": "0.4",
"size": "4",
"type": "square"
}, {
"name": "venezuela",
"group": "0.7",
"size": "7",
"type": "square"
}],
"links": [{
"source": 1,
"target": 0,
"value": 1
}, {
"source": 2,
"target": 0,
"value": 2
}, {
"source": 2,
"target": 1,
"value": 3
}, {
"source": 1,
"target": 3,
"value": 4
}, {
"source": 0,
"target": 3,
"value": 5
}, {
"source": 4,
"target": 3,
"value": 1
}, {
"source": 6,
"target": 5,
"value": 2
}, {
"source": 7,
"target": 5,
"value": 3
}, {
"source": 7,
"target": 8,
"value": 4
}, {
"source": 5,
"target": 8,
"value": 5
}, {
"source": 9,
"target": 8,
"value": 1
}, {
"source": 29,
"target": 28,
"value": 5
}, {
"source": 26,
"target": 30,
"value": 5
}, {
"source": 29,
"target": 30,
"value": 5
}, {
"source": 28,
"target": 31,
"value": 5
}, {
"source": 30,
"target": 31,
"value": 5
}, {
"source": 29,
"target": 31,
"value": 5
}, {
"source": 15,
"target": 32,
"value": 5
}, {
"source": 33,
"target": 32,
"value": 5
}, {
"source": 15,
"target": 33,
"value": 5
}, {
"source": 30,
"target": 33,
"value": 5
}, {
"source": 32,
"target": 34,
"value": 5
}, {
"source": 32,
"target": 35,
"value": 5
}, {
"source": 36,
"target": 35,
"value": 5
}, {
"source": 30,
"target": 37,
"value": 5
}, {
"source": 31,
"target": 37,
"value": 5
}, {
"source": 32,
"target": 37,
"value": 5
}, {
"source": 33,
"target": 37,
"value": 5
}, {
"source": 35,
"target": 37,
"value": 5
}, {
"source": 34,
"target": 36,
"value": 5
}, {
"source": 37,
"target": 38,
"value": 5
}, {
"source": 39,
"target": 38,
"value": 5
}, {
"source": 41,
"target": 40,
"value": 5
}, {
"source": 39,
"target": 40,
"value": 5
}, {
"source": 25,
"target": 41,
"value": 0.5
}, {
"source": 38,
"target": 41,
"value": 2
}]
}