D3 Nodes not appearing while updating it realtime - d3.js
I am playing with the Mike Bostock's mobile patent suits example
I am adding ~100 nodes using forEach loop in this JSFiddle; In reality, these nodes will be passed by an external service in real-time.
The page is loading a few static nodes initially. The reset button calls the forEach loop to create the new nodes using the function add_prc. This method pushes the node to nodes list (used to display in SVG) and then calls the refresh method but the nodes do not appear on the screen properly.
I see a dot at top-left of the screen but I can't select it or drag it to the center.
Once you add the nodes/links using the forEach loop, you aren't translating them anywhere. Check out this screenshot of the console:
As I mentioned in the comments, the tick function applies transform to the previously added path, circle and text but does it add anything to the newly added nodes, links? NO. So that's the thing that's missing. As I know your previous question, I'm adding the code from that to this:
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
Now, this would add all nodes at (100, 100) (I know you're just testing things). Anyway, I've used random x and ys. Here's a snippet and a JSFIDDLE
.node {
fill: #000;
}
.cursor {
fill: green;
stroke: brown;
pointer-events: none;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
#licensing {
fill: green;
}
.link.licensing {
stroke: green;
}
.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: green;
stroke: red;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
<button id="reset" onclick="reset()">reset</button>
<button id="ref" onclick="refresh()">refresh</button>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960, height = 500;
var links = [{source:"simulator",target:"monitor" ,type:"resolved"} , {source:"web",target:"monitor" ,type:"resolved"} ];
var links1 = [{"source":"ctfa","target":"tfa"},
{"source":"cea","target":"tea"},
{"source":"ctfe","target":"tfe"},
{"source":"ctee","target":"tee"},
{"source":"ctfu","target":"tfu"},
{"source":"cteu","target":"teu"},
{"source":"rfa","target":"tfa"},
{"source":"rea","target":"tea"},
{"source":"rfe","target":"tfe"},
{"source":"ree","target":"tee"},
{"source":"rfu","target":"tfu"},
{"source":"reu","target":"teu"},
{"source":"r1fa","target":"rfa"},
{"source":"r1fa","target":"gfa"},
{"source":"r1fa","target":"ggf"},
{"source":"r1ea","target":"rea"},
{"source":"r1ea","target":"gea"},
{"source":"r1ea","target":"gge"},
{"source":"r1fe","target":"rfe"},
{"source":"r1fe","target":"gfe"},
{"source":"r1fe","target":"ggf"},
{"source":"r1ee","target":"ree"},
{"source":"r1ee","target":"gee"},
{"source":"r1ee","target":"gge"},
{"source":"r1fu","target":"rfu"},
{"source":"r1fu","target":"gfu"},
{"source":"r1fu","target":"ggf"},
{"source":"r1eu","target":"reu"},
{"source":"r1eu","target":"geu"},
{"source":"r1eu","target":"gge"},
{"source":"hh1fa","target":"ggf"},
{"source":"hh1ea","target":"gge"},
{"source":"hh1fe","target":"ggf"},
{"source":"hh1ee","target":"gge"},
{"source":"hh1fu","target":"ggf"},
{"source":"hh1eu","target":"gge"},
{"source":"dbfa","target":"gfa"},
{"source":"dbea","target":"gea"},
{"source":"dbfe","target":"gfe"},
{"source":"dbee","target":"gee"},
{"source":"dbfu","target":"gfu"},
{"source":"dbeu","target":"geu"},
{"source":"hflse","target":"tee"},
{"source":"hfnyse","target":"teu"},
{"source":"hfnse","target":"teu"},
{"source":"hfret","target":"tfu"},
{"source":"hfebs","target":"tfe"},
{"source":"hfint","target":"tfu"},
{"source":"c1e","target":"ctee"},
{"source":"c1e","target":"gge"},
{"source":"c2e","target":"ctee"},
{"source":"c3e","target":"cteu"},
{"source":"c4e","target":"cteu"},
{"source":"c5e","target":"ggf"},
{"source":"d1e","target":"ctee"},
{"source":"c1f","target":"ctfe"},
{"source":"c2f","target":"ctfe"},
{"source":"c3f","target":"ggf"},
{"source":"c4f","target":"gge"},
{"source":"c5f","target":"ctfa"},
{"source":"d1f","target":"ctfe"}];
var nodes1 = [{"id":"tfa"},
{"id":"tea"},
{"id":"tfe"},
{"id":"tee"},
{"id":"tfu"},
{"id":"teu"},
{"id":"ctfa"},
{"id":"cea"},
{"id":"ctfe"},
{"id":"ctee"},
{"id":"ctfu"},
{"id":"cteu"},
{"id":"rfa"},
{"id":"rea"},
{"id":"rfe"},
{"id":"ree"},
{"id":"rfu"},
{"id":"reu"},
{"id":"r1fa"},
{"id":"r1ea"},
{"id":"r1fe"},
{"id":"r1ee"},
{"id":"r1fu"},
{"id":"r1eu"},
{"id":"hh1fa"},
{"id":"hh1ea"},
{"id":"hh1fe"},
{"id":"hh1ee"},
{"id":"hh1fu"},
{"id":"hh1eu"},
{"id":"dbfa"},
{"id":"dbea"},
{"id":"dbfe"},
{"id":"dbee"},
{"id":"dbfu"},
{"id":"dbeu"},
{"id":"gfa"},
{"id":"gea"},
{"id":"gfe"},
{"id":"gee"},
{"id":"gfu"},
{"id":"geu"},
{"id":"gge"},
{"id":"ggf"},
{"id":"hflse"},
{"id":"hfnyse"},
{"id":"hfnse"},
{"id":"hfret"},
{"id":"hfebs"},
{"id":"hfint"},
{"id":"c1e"},
{"id":"c2e"},
{"id":"c3e"},
{"id":"c4e"},
{"id":"c5e"},
{"id":"d1e"},
{"id":"c1f"},
{"id":"c2f"},
{"id":"c3f"},
{"id":"c4f"},
{"id":"c5f"},
{"id":"d1f"}];
var nodes = [ {"id":"monitor", "grp":"system"}, {"id":"simulator", "grp":"system"}, {id:"web", grp:"client"}];
function reset() {
nodes1.forEach(function(d){ add_prc(d) });
links1.forEach(function(d){ add_con(d) });
}
function add_prc(newNode) {
//console.log(newNode);
addNodeCanvas(newNode.id,newNode.grp);
}
function add_con(newConnection) {
//console.log(newConnection);
addLinkCanvas( newConnection.source,newConnection.target);
}
//setInterval(refresh, 15000);
function addNodeCanvas(nodeName,g) {
var node1 = { x: Math.floor(Math.random()*200+100), y: Math.floor(Math.random()*200+100), id: nodeName, grp:g };
var n = nodes.push(node1);
//console.log(node1);
refresh();
}
function addLinkCanvas(idSrc, idTarget) {
if (idSrc != idTarget) {
var s = {}, t = {};
nodes.forEach(function(curNode) {
if (typeof curNode.id != "undefined") {
if (curNode.id == idSrc) { s = curNode; }
if (curNode.id == idTarget) { t = curNode; }
}
});
//console.log( { s,t});
links.push({ source: s, target: t });
};
refresh();
}
var width = 900,
height = 600,
radius = 8;
var map = {}
nodes.forEach(function(d,i){
map[d.id] = i;
})
links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(50)
.charge(-200)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "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");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.id; });
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function mousedownNode(d, i) {
nodes.splice(i, 1);
links = links.filter(function(l) {
return l.source !== d && l.target !== d;
});
d3.event.stopPropagation();
refresh();
}
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
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("transform", function(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;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
function refresh(){
node = node.data(nodes);
node.enter().insert("circle", ".cursor")
.attr("class", "node")
.attr("r", 5)
.on("mousedown", mousedownNode);
node.exit()
.remove();
link = link.data(links);
link.enter().insert("line", ".node")
.attr("class", "link");
link.exit()
.remove();
force.start();
}
</script>
Related
How to drag nodes with children in d3 circle packing?
I have been trying to drag in d3 packed nodes using this code as a basis https://codepen.io/MrHen/pen/GZQOPW. Unfortunately I can't find a method to use so that when a node with children is dragged around it's (visible) children move with the parent. I use this function to drag circles around: var drag = d3.behavior.drag() .on("drag", function(d, i) { d.x += d3.event.dx; d.y += d3.event.dy; draw(); }) I the example above for instance I want to be able to drag the nodes on layer 1 (light blue) and when I do this their children change their position so they stay (visually) in the borders of their parents. Thank you!
First remove the pointer-events: none on the root and middle nodes. Then set yourself up a little recursion to walk a node's descendants and update their position: function recurOnChildren(d) { d.x += d3.event.dx; d.y += d3.event.dy; if (!d.children) return; d.children.forEach(c => { recurOnChildren(c); }); } And call from your drag handler: var drag = d3.behavior.drag() .on("drag", function(d, i) { recurOnChildren(d); draw(); }) Running code: var margin = 20, diameter = 960; var color = d3.scale.linear() .domain([-1, 5]) .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"]) .interpolate(d3.interpolateHcl); var pack = d3.layout.pack() .padding(2) .size([diameter - margin, diameter - margin]) .value(function(d) { return d.size; }) var svg = d3.select("body").append("svg") .attr("width", diameter) .attr("height", diameter) .append("g") .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")"); d3.json("https://gist.githubusercontent.com/mbostock/7607535/raw/695f8ed6298c06a946406c707200a1f6b21875d8/flare.json", function(error, root) { if (error) throw error; var focus = root, nodes = pack.nodes(root), view; function recurOnChildren(d) { d.x += d3.event.dx; d.y += d3.event.dy; if (!d.children) return; d.children.forEach(c => { recurOnChildren(c); }); } var drag = d3.behavior.drag() .on("drag", function(d, i) { recurOnChildren(d); draw(); }) var circle = svg.selectAll("circle") .data(nodes) .enter().append("circle") .attr("class", function(d) { return d.parent ? d.children ? "node node--middle" : "node node--leaf" : "node node--root"; }) .style("fill", function(d) { return d.children ? color(d.depth) : null; }); var text = svg.selectAll("text") .data(nodes) .enter().append("text") .attr("class", "label") .style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; }) .style("display", function(d) { return d.parent === root ? "inline" : "none"; }) .text(function(d) { return d.name; }); var node = svg.selectAll("circle,text"); svg.selectAll(".node").call(drag); d3.select("body") .style("background", color(-1)) draw(); function draw() { var k = diameter / (root.r * 2 + margin); node.attr("transform", function(d) { return "translate(" + (d.x - root.x) * k + "," + (d.y - root.y) * k + ")"; }); circle.attr("r", function(d) { return d.r * k; }); } }); .node { cursor: pointer; } .node:hover { stroke: #000; stroke-width: 1.5px; } .node--leaf { fill: white; } .label { font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif; text-anchor: middle; text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff; } .label { pointer-events: none; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
d3.js graphics not showing in Jupyter notebook output HTML
I am using a d3.js visualisation that works perfectly in the Jupyter notebook with the d3 cell magic. However, when I output the notebook to html, I just get a blank space. Below is the code. Does anyone know how to make this work with the html output? Ideally I would like the chart to remain interactive. %%d3 4.1 <!DOCTYPE html> <meta charset="utf-8"> <style> .node { cursor: pointer; } .node:hover { stroke: #000; stroke-width: 1.5px; } .node--leaf { fill: white; } .label { font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif; text-anchor: middle; text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff; } .label, .node--root, .node--leaf { pointer-events: none; } </style> <svg width="760" height="760"></svg> <script> require.config({ paths: { d3: "https://d3js.org/d3.v4.min.js" } }); require(["d3"], function(d3) { console.log(d3); var svg = d3.select("svg"), margin = 20, diameter = +svg.attr("width"), g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")"); var color = d3.scaleSequential(d3.interpolateViridis) .domain([-4, 4]); var pack = d3.pack() .size([diameter - margin, diameter - margin]) .padding(2); d3.json("output.json", function(error, root) { if (error) throw error; root = d3.hierarchy(root) .sum(function(d) { return d.size; }) .sort(function(a, b) { return b.value - a.value; }); var focus = root, nodes = pack(root).descendants(), view; var circle = g.selectAll("circle") .data(nodes) .enter().append("circle") .attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; }) .style("fill", function(d) { return d.children ? color(d.depth) : null; }) .on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); }); var text = g.selectAll("text") .data(nodes) .enter().append("text") .attr("class", "label") .style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; }) .style("display", function(d) { return d.parent === root ? "inline" : "none"; }) .text(function(d) { return d.data.name; }); var node = g.selectAll("circle,text"); svg .style("background", color(-1)) .on("click", function() { zoom(root); }); zoomTo([root.x, root.y, root.r * 2 + margin]); function zoom(d) { var focus0 = focus; focus = d; var transition = d3.transition() .duration(d3.event.altKey ? 7500 : 750) .tween("zoom", function(d) { var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]); return function(t) { zoomTo(i(t)); }; }); transition.selectAll("text") .filter(function(d) { return d.parent === focus || this.style.display === "inline"; }) .style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; }) .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; }) .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; }); } function zoomTo(v) { var k = diameter / v[2]; view = v; node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; }); circle.attr("r", function(d) { return d.r * k; }); } }); }); </script>
The data that you are use to input here: d3.json("output.json", function(error, root) {if (error) throw error;" Are you sure that is on the right path? For me, I got a similar problem and was because the input data path was incorrect.
Wrapping multi line labels
I'm building a responsive zoomable treemap based on this project. The problem is that the labels I've are longer than the original visualization and end up not showing: function text(text) { text.selectAll("tspan") .attr("x", function(d) { return x(d.x) + 6; }) text.attr("x", function(d) { return x(d.x) + 6; }) .attr("y", function(d) { return y(d.y) + 6; }) .style("opacity", function(d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; }); } The problem I see is that the text needs to be showed completely in the first line, but I'd like to show it in multiple lines (inside the rect) instead. There's a code by Mike Bostock which seems to solve this issue but I don't know how to apply it to the treemap.
Here's a quick a modification which wraps the parent text in that example: <!DOCTYPE html> <!-- Generic treemap, based on http://bost.ocks.org/mike/treemap/ --> <html> <head> <meta charset="utf-8"> <title>Zoomable treemap template</title> <style> #chart { background: #fff; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } .title { font-weight: bold; font-size: 24px; text-align: center; margin-top: 6px; margin-bottom: 6px; } text { pointer-events: none; } .grandparent text { font-weight: bold; } rect { fill: none; stroke: #fff; } rect.parent, .grandparent rect { stroke-width: 2px; } rect.parent { pointer-events: none; } .grandparent rect { fill: orange; } .grandparent:hover rect { fill: #ee9700; } .children rect.parent, .grandparent rect { cursor: pointer; } .children rect.parent { fill: #bbb; fill-opacity: .5; } .children:hover rect.child { fill: #bbb; } </style> </head> <body> <div id="chart"></div> <script src="http://code.jquery.com/jquery-1.7.2.min.js"></script> <script src="http://d3js.org/d3.v3.min.js"></script> <script> window.addEventListener('message', function(e) { var opts = e.data.opts, data = e.data.data; return main(opts, data); }); var defaults = { margin: { top: 24, right: 0, bottom: 0, left: 0 }, rootname: "TOP", format: ",d", title: "", width: 960, height: 500 }; function main(o, data) { var root, opts = $.extend(true, {}, defaults, o), formatNumber = d3.format(opts.format), rname = opts.rootname, margin = opts.margin, theight = 36 + 16; $('#chart').width(opts.width).height(opts.height); var width = opts.width - margin.left - margin.right, height = opts.height - margin.top - margin.bottom - theight, transitioning; var color = d3.scale.category20c(); var x = d3.scale.linear() .domain([0, width]) .range([0, width]); var y = d3.scale.linear() .domain([0, height]) .range([0, height]); var treemap = d3.layout.treemap() .children(function(d, depth) { return depth ? null : d._children; }) .sort(function(a, b) { return a.value - b.value; }) .ratio(height / width * 0.5 * (1 + Math.sqrt(5))) .round(false); var svg = d3.select("#chart").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.bottom + margin.top) .style("margin-left", -margin.left + "px") .style("margin.right", -margin.right + "px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .style("shape-rendering", "crispEdges"); var grandparent = svg.append("g") .attr("class", "grandparent"); grandparent.append("rect") .attr("y", -margin.top) .attr("width", width) .attr("height", margin.top); grandparent.append("text") .attr("x", 6) .attr("y", 6 - margin.top) .attr("dy", ".75em"); if (opts.title) { $("#chart").prepend("<p class='title'>" + opts.title + "</p>"); } if (data instanceof Array) { root = { key: rname, values: data }; } else { root = data; } initialize(root); accumulate(root); layout(root); console.log(root); display(root); if (window.parent !== window) { var myheight = document.documentElement.scrollHeight || document.body.scrollHeight; window.parent.postMessage({ height: myheight }, '*'); } function initialize(root) { root.x = root.y = 0; root.dx = width; root.dy = height; root.depth = 0; } // Aggregate the values for internal nodes. This is normally done by the // treemap layout, but not here because of our custom implementation. // We also take a snapshot of the original children (_children) to avoid // the children being overwritten when when layout is computed. function accumulate(d) { return (d._children = d.values) ? d.value = d.values.reduce(function(p, v) { return p + accumulate(v); }, 0) : d.value; } // Compute the treemap layout recursively such that each group of siblings // uses the same size (1×1) rather than the dimensions of the parent cell. // This optimizes the layout for the current zoom state. Note that a wrapper // object is created for the parent node for each group of siblings so that // the parent’s dimensions are not discarded as we recurse. Since each group // of sibling was laid out in 1×1, we must rescale to fit using absolute // coordinates. This lets us use a viewport to zoom. function layout(d) { if (d._children) { treemap.nodes({ _children: d._children }); d._children.forEach(function(c) { c.x = d.x + c.x * d.dx; c.y = d.y + c.y * d.dy; c.dx *= d.dx; c.dy *= d.dy; c.parent = d; layout(c); }); } } function display(d) { grandparent .datum(d.parent) .on("click", transition) .select("text") .text(name(d)); var g1 = svg.insert("g", ".grandparent") .datum(d) .attr("class", "depth"); var g = g1.selectAll("g") .data(d._children) .enter().append("g"); g.filter(function(d) { return d._children; }) .classed("children", true) .on("click", transition); var children = g.selectAll(".child") .data(function(d) { return d._children || [d]; }) .enter().append("g"); children.append("rect") .attr("class", "child") .call(rect) .append("title") .text(function(d) { return d.key + " (" + formatNumber(d.value) + ")"; }); children.append("text") .attr("class", "ctext") .text(function(d) { return d.key; }) .call(text2); g.append("rect") .attr("class", "parent") .call(rect); var t = g.append("text") .attr("class", "ptext") .attr("dy", ".75em") t.append("tspan") .text(function(d) { return d.key; }); t.append("tspan") .attr("dy", "1.0em") .text(function(d) { return formatNumber(d.value); }); t.call(text); g.selectAll("rect") .style("fill", function(d) { return color(d.key); }); function transition(d) { if (transitioning || !d) return; transitioning = true; var g2 = display(d), t1 = g1.transition().duration(750), t2 = g2.transition().duration(750); // Update the domain only after entering new elements. x.domain([d.x, d.x + d.dx]); y.domain([d.y, d.y + d.dy]); // Enable anti-aliasing during the transition. svg.style("shape-rendering", null); // Draw child nodes on top of parent nodes. svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; }); // Fade-in entering text. g2.selectAll("text").style("fill-opacity", 0); // Transition to the new view. t1.selectAll(".ptext").call(text).style("fill-opacity", 0); t1.selectAll(".ctext").call(text2).style("fill-opacity", 0); t2.selectAll(".ptext").call(text).style("fill-opacity", 1); t2.selectAll(".ctext").call(text2).style("fill-opacity", 1); t1.selectAll("rect").call(rect); t2.selectAll("rect").call(rect); // Remove the old node when the transition is finished. t1.remove().each("end", function() { svg.style("shape-rendering", "crispEdges"); transitioning = false; }); } return g; } function text(text) { text.selectAll("tspan") .attr("x", function(d) { return x(d.x) + 6; }) text.attr("x", function(d) { return x(d.x) + 6; }) .attr("y", function(d) { return y(d.y) + 6; }) .each(function(d) { var tspan = this.childNodes[0]; var w = x(d.x + d.dx) - x(d.x); wrap(tspan, w, x(d.x) + 6); }) } function text2(text) { text.attr("x", function(d) { return x(d.x + d.dx) - this.getComputedTextLength() - 6; }) .attr("y", function(d) { return y(d.y + d.dy) - 6; }) .style("opacity", function(d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; }); } function rect(rect) { rect.attr("x", function(d) { return x(d.x); }) .attr("y", function(d) { return y(d.y); }) .attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) .attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }); } function name(d) { return d.parent ? name(d.parent) + " / " + d.key + " (" + formatNumber(d.value) + ")" : d.key + " (" + formatNumber(d.value) + ")"; } } if (window.location.hash === "") { d3.json("https://jsonblob.com/api/7c30e101-da91-11e6-90ab-11c211a4b3d5", function(err, res) { if (!err) { console.log(res); var data = d3.nest().key(function(d) { return d.region; }).key(function(d) { return d.subregion; }).entries(res); main({ title: "World Population" }, { key: "World", values: data }); } }); } function wrap(tspan, width, x) { var text = d3.select(tspan), words = text.text().split(/\s+/).reverse(), word, line = [], y = text.attr("y"), dy = parseFloat(text.attr("dy")) || 0.4, tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", "0.75em"); while (word = words.pop()) { line.push(word); tspan.text(line.join(" ")); if (tspan.node().getComputedTextLength() > width) { line.pop(); tspan.text(line.join(" ")); line = [word]; tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", "1em").text(word); } } } </script> </body> </html>
Make D3 force directed graph responsive and adhere to bounding box
I have a force directed graph generated via D3 that isn't playing well with the responsive code or the bounding box code I've found. Since the radius of all of my circles varies, I think it's throwing some things off... Any help is appreciated! I have to use a custom length on the lines because the nodes run into each other if I don't manually space them out because the radii aren't the same. (Please don't link me to the d3 page with the code, I've tried it, but maybe I'm placing it in the wrong spot if you think it would work on this. I also tried to post an image, but I don't have enough reputation.) var width = 876, height = 600; var color = d3.scale.category20(); var force = d3.layout.force() .charge(-1010) .linkDistance(function(d) { return d.distance; }) .size([width, height]) .gravity(0.7); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var tip = d3.tip() .attr('class', 'd3-tip') .offset([-5, 0]) .html(function (d) { return d.name + " (" + d.instances + ")"; }) svg.call(tip); d3.json("datawords.json", function(error, graph) { force .nodes(graph.nodes) .links(graph.links) .start(); var link = svg.selectAll(".link") .data(graph.links) .enter().append("line") .attr("class", "link") .attr("width", function(d) { return d.totalLength; }) .style("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = svg.selectAll(".node") .data(graph.nodes) .enter().append("circle") .attr("class", "node") .attr("r", function(d) {return d.instances;}) .style("fill", function(d) { return color(d.instances); }) .call(force.drag) .on('mouseover', tip.show) .on('mouseout', tip.hide) .on('click', connectedNodes) force.on("tick", function() { 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; }); node.each(collide(0.5)) }); //Toggle stores whether the highlighting is on var toggle = 0; //Create an array logging what is connected to what 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; }); //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; }); //Reduce the op toggle = 1; } else { //Put them back to opacity=1 node.style("opacity", 1); link.style("opacity", 1); toggle = 0; }; }; var padding = 10, // separation between circles radius=15; function collide(alpha) { var quadtree = d3.geom.quadtree(graph.nodes); return function(d) { var rb = 4*radius + padding, nx1 = d.x - rb, nx2 = d.x + rb, ny1 = d.y - rb, ny2 = d.y + rb; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y); if (l < rb) { l = (l - rb) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; }; window.addEventListener('resize', resize); function resize() { width = window.innerWidth, height = window.innerHeight; svg.attr("width", width).attr("height", height); force.size([width, height]).resume(); } }); .node { stroke: #fff; stroke-width: 1.5px; } .link { stroke: #999; stroke-opacity: .6; } .node-active{ stroke: #555; stroke-width: 1.5px; } .node:hover{ stroke: #555; stroke-width: 1.5px; } marker { display:none; } .d3-tip { line-height: 1; font-weight: bold; padding: 12px; background: rgba(0, 0, 0, 0.8); color: #fff; border-radius: 2px; } .d3-tip.n:after { margin: -1px 0 0 0; top: 200%; left: 0; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <!DOCTYPE html> <meta charset="utf-8"> <body> <script src="d3/d3tip.js"></script> <div class="graph"></div> </body>
Draw curve line between nodes bugs in d3.js
I was trying to draw curves between two nodes using d3.js. <meta charset="utf-8"> <!-- ----------------- --> <!-- THIS PART IS CSS --> <!-- ----------------- --> <style> body { background: lightskyblue; } .node { stroke: black; stroke-width: 4.0px; } .link { stroke: slategray; stroke-opacity: 0.6; } </style> </head> <body> <!-- ------------------------ --> <!-- THIS PART IS JAVASCRIPT --> <!-- ------------------------ --> <script src="d3.v3.js"></script> <script> //////////////////////////////////////////////////////////////// // Function to prepare topology background //////////////////////////////////////////////////////////////// var width = 1280, height = 630; var color = d3.scale.category20(); var svg = d3.select("body") .append("svg") .attr("width", width) .attr("height", height) .style("border","7px solid black") ; var nodes = [], links = []; var graph = test_case(); nodes = graph.nodes; links = graph.links; for(var i = 0; i < nodes.length; i++) { element = nodes[i]; if(element.group == 0) { element.x = width/2; element.y = height/2; element.fixed = true; } } var force = d3.layout.force() .charge(-400) .linkDistance(300) .size([width, height]) .links(links) .nodes(nodes) .on("tick", tick); var node = svg.selectAll(".node"), link = svg.selectAll(".link"); drawGraph(); function test_case() { graph = { "nodes":[ {"name":0,"group":0,"pos":[216,140]}, {"name":1,"group":1,"pos":[350,200]}, {"name":2,"group":1,"pos":[478,438]}, {"name":3,"group":1,"pos":[596,498]}, {"name":4,"group":1,"pos":[422,295]}, {"name":5,"group":1,"pos":[597,357]}, {"name":6,"group":1,"pos":[530,238]}, {"name":7,"group":1,"pos":[506,100]}, {"name":8,"group":1,"pos":[793,265]}, {"name":9,"group":1,"pos":[972,228]} ], "links":[ {"source":1,"target":2,"rssi":25,"lqi":25,"lr":120}, {"source":2,"target":0,"rssi":50,"lqi":50,"lr":80}, {"source":3,"target":0,"rssi":12,"lqi":12,"lr":200}, {"source":3,"target":2,"rssi":65,"lqi":65,"lr":40}, {"source":4,"target":0,"rssi":80,"lqi":80,"lr":170}, {"source":5,"target":0,"rssi":60,"lqi":60,"lr":110}, {"source":6,"target":7,"rssi":30,"lqi":30,"lr":64}, {"source":7,"target":0,"rssi":21,"lqi":21,"lr":97}, {"source":8,"target":3,"rssi":57,"lqi":57,"lr":190}, {"source":9,"target":0,"rssi":72,"lqi":72,"lr":12} ] }; return graph; } //////////////////////////////////////////////////////////////// // Function to draw topology, including nodes and links //////////////////////////////////////////////////////////////// function drawGraph() { //Add links link = link.data(force.links()); link.enter() .append("path") // .insert("line", ".gnode") .attr("class", "link") // .style("stroke", function(d) { // if(d.rssi<=25) return "crimson"; // else if(d.rssi<=50) return "orange"; // else if(d.rssi<=75) return "royalblue"; // else return "green"; }) //.style("stroke-dasharray" ,function(d) { if(d.target==0) return "10,10"; else return "";}) // .style("stroke-width", function(d) { return d.lqi/20 + 5; }) // .style("stroke-opacity", function(d) { return (d.lr/51)*0.1 + 0.5; }); link.exit().remove(); //Add nodes with texts in them node = node.data(force.nodes()); node.enter().append("g").attr("class", "gnode").call(force.drag); node.append("circle") .attr("class", function(d) { return "node group" + d.group }) .attr("r", 25) .style("fill", function(d) { if(d.group==0) return "mediumaquamarine"; else return "beige" }); node.append("text") .attr("text-anchor", "middle") .attr("dy", "+7") .text(function(d) { return d.name; }) .attr("font-size", "22px") .attr("font-weight", "bold") .style("fill", "black"); node.exit().remove(); force.start(); } function tick() { node.attr("transform", function(d) { return "translate(" + d.pos + ")";}); link.attr("d", function(d) { var dx = d.target.pos[0] - d.source.pos[0], dy = d.target.pos[1] - d.source.pos[1], dr = Math.sqrt(dx * dx + dy * dy); return "M" + d.source.pos[0] + "," + d.source.pos[1] + "A" + dr + "," + dr + " 0 0,1 " + d.target.pos[0] + "," + d.target.pos[1]; }); } </script> </body> </html> but the result of the code was not as I expected which was supposed to be like this http://bl.ocks.org/d3noob/5155181 with addition of colours, opacity and width. I also want to draw arrows. Could anybody figure this out please? I'm sorry I'm very new to stackoverflow; it's my first time I have written this topic.
For adding arrow in path following steps: Append Marker: svg.append("svg:defs").selectAll("marker") .data(["end"]) // Different link/path types can be defined here .enter().append("svg:marker") // This section adds in the arrows .attr("id", String) .attr("viewBox", "0 -5 10 10") .attr("refX", 45) .attr("refY",-5.2) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto") .append("svg:path") .attr("d", "M0,-5L10,0L0,5"); Append arrow in Path link = link.data(force.links()); link.enter() .append("path") .attr("class", "link") .attr("marker-end", "url(#end)"); Path CSS: .link { stroke: slategray; stroke-opacity: 0.6; fill: none; stroke-width: 1.5px; } DEMO for your code According to radius you want to change the arrow position refer this ,and it will help for dragging nodes
First step: Make fill none :) .link { stroke: slategray; fill: none; stroke-opacity: 0.6; } Working on the arrows JSFIDDLE