D3 Javascript - Only showing one link in force layout - javascript
I'm trying to create something that allows the user to scroll through an array of different links. The nodes stay the same, but the links change when the user clicks to see the next one.
However, it only shows one link in each array. Some arrays only have one link so that's okay, but the ones with more than one link need to be displayed correctly.
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//var links = <?php echo json_encode(array_slice($data["links"], 0, 1)); ?>;
var links = <?php echo json_encode($links); ?>;
var nodes = <?php echo json_encode($nodes); ?>;
var count = <?php echo count($data); ?>;
var data_set = <?php echo json_encode($data); ?>;
//console.log(data_set);
//document.getElementById("demo").innerHTML = links;
max_count = links.length;
function draw()
{
function update_clique_view(direction)
{
geo_long_12_index += direction;
if(geo_long_12_index > max_count)
geo_long_12_index = 0;
else if(geo_long_12_index < 0)
geo_long_12_index = max_count - 1;
current_clique = links[geo_long_12_index];
d3.select("#link_number").remove();
svg.append("text")
.attr("id", "link_number")
.attr("x", width/2)
.attr("y", 20)
.attr("dy", ".35em")
.attr("opacity", 0.7)
.style("fill", "#FFFFFF")
.text(geo_long_12_index);
console.log(current_clique);
link.data(current_clique);
force.links(current_clique);
force.start();
}
var width = 800,
height = 600;
geo_long_12_index = 0;
if(links.length > 0)
{
current_clique = links[geo_long_12_index];
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("rx", "20")
.attr("ry", "20")
.style("fill", "#000022")
.attr("class", "background");
svg.append("text")
.attr("id", "link_number")
.attr("x", width/2)
.attr("y", 20)
.attr("dy", ".35em")
.attr("opacity", 0.7)
.style("fill", "#FFFFFF")
.text(geo_long_12_index);
// Navigation arrows
var leftarrow = svg.append("svg:image")
.attr("xlink:href", "/cgi-bin/images/left_arrow.svg")
.attr("width", 20)
.attr("height", 20)
.attr("x", (width / 2) - 25)
.attr("y",20)
.attr("opacity", 0.7)
.on("click", function(d,i) {update_clique_view(-1);})
.on("mouseover", function(d) {d3.select(this).attr("opacity", 1);})
.on("mouseout", function(d) {d3.select(this).attr("opacity", 0.7);});
var rightarrow = svg.append("svg:image")
.attr("xlink:href","/cgi-bin/images/right_arrow.svg")
.attr("width", 20)
.attr("height", 20)
.attr("x", (width / 2) + 35)
.attr("y",20)
.attr("opacity", 0.7)
.on("click", function(d,i) {update_clique_view(1);})
.on("mouseover", function(d) {d3.select(this).attr("opacity", 1);})
.on("mouseout", function(d) {d3.select(this).attr("opacity", 0.7);});
var position_label = svg.append("text")
.attr("x", 10)
.attr("y", 30);
var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([width, height]);
force
.nodes(nodes)
.links(current_clique)
.start();
var link = svg.selectAll(".link")
.data(current_clique)
.enter().append("line")
.style("stroke", function(d) { return d.colour; })
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 5)
.style("fill", function(d) { return d.colour; })
.attr("class", "planet")
.on("mouseover", function(d) {d3.select(this).attr("r", 10);})
.on("mouseout", function(d) {d3.select(this).attr("r", 5);});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
}
draw();
</script>
var links = [[{"source":17,"target":22,"orb":0.058311111111109,"colour":"grey"}],[{"source":20,"target":21,"orb":0.2079,"colour":"grey"}],[{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":3,"target":21,"orb":1.0416277777778,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":0,"target":17,"orb":0.71342777777778,"colour":"grey"},{"source":0,"target":22,"orb":0.77173888888888,"colour":"grey"},{"source":17,"target":22,"orb":0.058311111111109,"colour":"grey"}],[{"source":11,"target":14,"orb":-1.6950111111111,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":7,"target":8,"orb":0.41579999999996,"colour":"grey"},{"source":7,"target":12,"orb":-0.047427777777784,"colour":"grey"},{"source":7,"target":15,"orb":0.6553222222222,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":8,"target":12,"orb":0.46322777777775,"colour":"grey"},{"source":4,"target":7,"orb":-0.53862777777772,"colour":"grey"},{"source":4,"target":8,"orb":-0.12282777777776,"colour":"grey"},{"source":4,"target":12,"orb":-0.5860555555555,"colour":"grey"},{"source":4,"target":15,"orb":0.11669444444448,"colour":"grey"},{"source":7,"target":8,"orb":0.41579999999996,"colour":"grey"},{"source":7,"target":12,"orb":-0.047427777777784,"colour":"grey"},{"source":7,"target":15,"orb":0.6553222222222,"colour":"grey"},{"source":8,"target":15,"orb":0.23952222222223,"colour":"grey"},{"source":12,"target":15,"orb":0.70274999999998,"colour":"grey"}],[{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":19,"orb":-1.8974666666667,"colour":"grey"},{"source":13,"target":19,"orb":2.8450944444444,"colour":"grey"}],[{"source":11,"target":20,"orb":-2.9160166666667,"colour":"grey"},{"source":11,"target":21,"orb":-2.7081166666667,"colour":"grey"},{"source":20,"target":21,"orb":0.2079,"colour":"grey"}],[{"source":3,"target":9,"orb":-2.1064388888889,"colour":"grey"},{"source":3,"target":20,"orb":0.83372777777777,"colour":"grey"},{"source":9,"target":20,"orb":-2.9401666666667,"colour":"grey"}],[{"source":2,"target":9,"orb":2.9672611111111,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":9,"target":13,"orb":2.0196333333333,"colour":"grey"},{"source":9,"target":16,"orb":1.6376055555556,"colour":"grey"},{"source":9,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":2,"target":5,"orb":-2.4144611111111,"colour":"grey"},{"source":2,"target":6,"orb":1.3803722222222,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":2,"target":19,"orb":-1.8974666666667,"colour":"grey"},{"source":5,"target":6,"orb":-1.0340888888889,"colour":"grey"},{"source":5,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":5,"target":19,"orb":-0.51699444444445,"colour":"grey"},{"source":6,"target":18,"orb":1.6567722222222,"colour":"grey"},{"source":6,"target":19,"orb":-0.51709444444444,"colour":"grey"},{"source":18,"target":19,"orb":2.1738666666667,"colour":"grey"}],[{"source":1,"target":2,"orb":-1.2271277777778,"colour":"grey"},{"source":1,"target":6,"orb":-2.6075,"colour":"grey"},{"source":1,"target":13,"orb":-0.27950000000001,"colour":"grey"},{"source":1,"target":16,"orb":0.10252777777777,"colour":"grey"},{"source":1,"target":18,"orb":-0.95072777777779,"colour":"grey"},{"source":2,"target":6,"orb":1.3803722222222,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":6,"target":13,"orb":2.328,"colour":"grey"},{"source":6,"target":16,"orb":2.7100277777778,"colour":"grey"},{"source":6,"target":18,"orb":1.6567722222222,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}],[{"source":1,"target":2,"orb":-1.2271277777778,"colour":"grey"},{"source":1,"target":9,"orb":1.7401333333333,"colour":"grey"},{"source":1,"target":13,"orb":-0.27950000000001,"colour":"grey"},{"source":1,"target":16,"orb":0.10252777777777,"colour":"grey"},{"source":1,"target":18,"orb":-0.95072777777779,"colour":"grey"},{"source":2,"target":9,"orb":2.9672611111111,"colour":"grey"},{"source":2,"target":13,"orb":0.94762777777777,"colour":"grey"},{"source":2,"target":16,"orb":1.3296555555555,"colour":"grey"},{"source":2,"target":18,"orb":0.2764,"colour":"grey"},{"source":9,"target":13,"orb":2.0196333333333,"colour":"grey"},{"source":9,"target":16,"orb":1.6376055555556,"colour":"grey"},{"source":9,"target":18,"orb":2.6908611111111,"colour":"grey"},{"source":13,"target":16,"orb":-0.38202777777778,"colour":"grey"},{"source":13,"target":18,"orb":-0.67122777777777,"colour":"grey"},{"source":16,"target":18,"orb":-1.0532555555556,"colour":"grey"}]];
var nodes = [{"name":"EARTHSUN","colour":"darkgreen"},{"name":"MERCURY","colour":"cyan"},{"name":"VENUS","colour":"magenta"},{"name":"MARS","colour":"red"},{"name":"JUPITER","colour":"gold"},{"name":"SATURN","colour":"darkslategray"},{"name":"URANUS","colour":"blue"},{"name":"NEPTUNE","colour":"blueviolet"},{"name":"PLUTO","colour":"maroon"},{"name":"CHIRON","colour":"orange"},{"name":"CERES","colour":"sandybrown"},{"name":"JUNO","colour":"orchid"},{"name":"PALLAS","colour":"royalblue"},{"name":"VESTA","colour":"pink"},{"name":"SAPPHO","colour":"plum"},{"name":"SEDNA","colour":"slategray"},{"name":"MARSSATURN","colour":null},{"name":"JUPITERSATURN","colour":"brown"},{"name":"SATURNCHIRON","colour":"indianred"},{"name":"SATURNURANUS","colour":null},{"name":"SATURNNEPTUNE","colour":null},{"name":"SATURNPLUTO","colour":null},{"name":"SATURNSEDNA","colour":null}];
Thanks for looking at this.
Related
Using D3.js fill node circle with a base64 jpeg image that is not a URL
I am trying to use D3.js on an html page and fill in the node circles with a .jpeg image that is in base64 format and is ** not a URL **. I have found lots of examples with URLs and have made a working Network Force Graph with URLs but I need to be able to just pass base64 in my json file. Can anyone help me? Thank you. Here is the relevant snippet. d.img contains the base64 text.: var images = node.append("svg:image") .attr("xlink:href", "data:image/jpeg;base64," + function(d) { return "data:image/jpeg;base64," + d.img;}) //.attr("xlink:href", function(d) { return "data:image/jpeg;base64," + "data:image/jpeg;base64," + d.img;}) .attr("x", function(d) { return -25;}) .attr("y", function(d) { return -25;}) .attr("height", 50) .attr("width", 50); Here is the full javascript file: <!DOCTYPE html> <head> <meta charset="utf-8"> <meta name="description" content=""> <title>8.6 - Node-link diagrams</title> <!-- Bootstrap Core CSS --> <link rel="stylesheet" href="css/bootstrap.min.css"> <!-- Custom CSS --> <link rel="stylesheet" href="css/style.css"> </head> <body> <nav class="navbar navbar-default"></nav> <svg width="600" height="600"></svg> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"), circleRadius = 30, chargeStrength = -5000; // -50 var color = d3.scaleOrdinal(d3.schemeCategory10); // Add "forces" to the simulation here var simulation = d3.forceSimulation() .force("center", d3.forceCenter(width / 2, height / 2)) .force("charge", d3.forceManyBody().strength(chargeStrength)) .force("collide", d3.forceCollide(circleRadius).strength(0.9)) .force("link", d3.forceLink().id(function(d) { return d.id; })); // For each id. d3.json("data/force2.json", function(error, graph) { if (error) throw error; console.log(graph); // Add lines for every link in the dataset var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("stroke-width", function(d) { return Math.sqrt(d.value); }); // Add circles for every node in the dataset var node = svg.append("g") .attr("class", "nodes") .selectAll("circle") .data(graph.nodes) .enter() .append("g") .attr("class", "node"); var circles = node.append("circle") .attr("r", circleRadius) .attr("fill", function(d) { return color(d.group); }) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended) ); // Add Supervisory Org Label var text2 = node.append("text") .style("text-anchor", "middle") .attr("y", 40) .text(function(d) {return d.org }); // Add id (name) label var text = node.append("text") .style("text-anchor", "middle") .attr("y", 55) .text(function(d) {return d.id }); var images = node.append("svg:image") .attr("xlink:href", "data:image/jpeg;base64," + function(d) { return "data:image/jpeg;base64," + d.img;}) //.attr("xlink:href", function(d) { return d.img;}) .attr("x", function(d) { return -25;}) .attr("y", function(d) { return -25;}) .attr("height", 50) .attr("width", 50); // make the image grow a little on mouse over and add the text details on click var setEvents = images .on( 'mouseenter', function() { // select element in current context d3.select( this ) .transition() .attr("x", function(d) { return -60;}) .attr("y", function(d) { return -60;}) .attr("height", 100) .attr("width", 100); }) // set back .on( 'mouseleave', function() { d3.select( this ) .transition() .attr("x", function(d) { return -25;}) .attr("y", function(d) { return -25;}) .attr("height", 50) .attr("width", 50); }); // Basic tooltips node.append("title") .text(function(d) { return d.id; }); // Attach nodes to the simulation, add listener on the "tick" event simulation .nodes(graph.nodes) .on("tick", ticked); // Associate the lines with the "link" force simulation.force("link") .links(graph.links) // Dynamically update the position of the nodes/links as time passes 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; }); //node //.attr("cx", function(d) { return d.x; }) // .attr("cy", function(d) { return d.y; }); node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); } var legend = svg.selectAll(".legend") //.data(color.domain()) .data([1,2]) .enter().append("g") .attr("class", "legend") //.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 100) .attr("y", height - 90) .attr("width", 18) .attr("height", 18) .style("fill", function(d) { return color(1) }); legend.append("rect") .attr("x", width - 100) .attr("y", height - 113) .attr("width", 18) .attr("height", 18) .style("fill", function(d) { return color(2) }); legend.append("text") .attr("x", width - 78) .attr("y", height - 80) .attr("dy", ".35em") .style("text-anchor", "begin") .text("MatchUP") //.text("MatchUPXXXXXXXXXXXXXXXXXXXX"); legend.append("text") .attr("x", width - 78) .attr("y", height - 103) .attr("dy", ".35em") .style("text-anchor", "begin") .text("Connection") }); // Change the value of alpha, so things move around when we drag a node function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.7).restart(); d.fx = d.x; d.fy = d.y; } // Fix the position of the node that we are looking at function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } // Let the node do what it wants again once we've looked at it function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } </script> </body>
the function must be: function(d) { return "data:image/jpeg;base64," + d.img; } otherwise you concatenate a string and a function which results in a string and you therefore losse the function.
D3 incorrect drag behaviour
This is a follow on from my previous question d3 rect in one group interfering with rect in another group Two issues: Incorrect drag behaviour. When clicking on the second rect to drag it, it jumps to where the third one is. I added a anonymous function which runs when the svg in clicked on anywhere. This should add a new rect. However that is the working. I know I should have only one issue per question but these are related and I suspect they will be solved together. <!DOCTYPE html> <meta charset="utf-8"> <style> /*.active { stroke: #000; stroke-width: 2px; }*/ </style> <svg width="960" height="500"></svg> <script src="//d3js.org/d3.v4.min.js"></script> <script> var margin = { top: 20, right: 20, bottom: 20, left: 20 }, width = 600 - margin.left - margin.right, height = 600 - margin.top - margin.bottom; var svg = d3.select("svg"); var data = [{ x: 200 }, { x: 300 }, { x: 400 }]; var groove = svg.append("g") .attr("class", "groove_group"); groove.append("rect") .attr("x", 100) .attr("y", 150) .attr("rx", 2) .attr("ry", 2) .attr("height", 6) .attr("width", 800) .style("fill", "grey"); groove.append("rect") .attr("x", 102) .attr("y", 152) .attr("rx", 2) .attr("ry", 2) .attr("height", 2) .attr("width", 796) .style("fill", "black"); // create group var group = svg.selectAll(null) .data(data) .enter().append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .on("click", removeElement); group.append("rect") .attr("x", function(d) { return d.x; }) .attr("y", 100) .attr("height", 100) .attr("width", 15) .style("fill", "lightblue") .attr('id', function(d, i) { return 'handle_' + i; }) .attr("rx", 6) .attr("ry", 6) .attr("stroke-width", 2) .attr("stroke", "black"); group.append("text") .attr("x", function(d) { return d.x }) .attr("y", 100) .attr("text-anchor", "start") .style("fill", "black") .text(function(d) { return "x:" + d.x }); // create group var group = svg.selectAll("g") .data(data) .enter().append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .on("click", removeElement); group.append("rect") .attr("x", function(d) { return d.x; }) .attr("y", 200) .attr("height", 100) .attr("width", 15) .style("fill", "lightblue") .attr('id', function(d, i) { return 'handle_' + i; }) .attr("rx", 6) .attr("ry", 6) .attr("stroke-width", 2) .attr("stroke", "black"); group.append("text") .attr("x", function(d) { return d.x }) .attr("y", 200) .attr("text-anchor", "start") .style("fill", "black") .text(function(d) { return "x:" + d.x }); svg.on("click", function() { var coords = d3.mouse(this); var newData = { x: d3.event.x, } data.push(newData); group.selectAll("rect") .data(data) .enter() .append("rect") .attr("x", function(d) { return d.x; }) .attr("y", 200) .attr("height", 100) .attr("width", 15) .style("fill", "steelblue") .attr('id', function(d, i) { return 'rect_' + i; }) .attr("rx", 6) .attr("ry", 6) .attr("stroke-width", 2) .attr("stroke", "black"); }); function dragstarted(d) { d3.select(this).raise().classed("active", true); } function dragged(d) { d3.select(this).select("text") .attr("x", d.x = d3.event.x); d3.select(this).select("rect") .attr("x", d.x = d3.event.x); } function dragended(d) { d3.select(this) .classed("active", false); } function removeElement(d) { d3.event.stopPropagation(); data = data.filter(function(e) { return e != d; }); d3.select(this) .remove(); } </script>
Here are the explanations to your issues: You are reassigning var groups, that is, you have two var groups in your code, the last one overwriting the first one. Just remove the last variable. In your function to append new rectangles, you are using an update selection that selects rectangles. However, your enter selection appends groups (<g>) elements, not rectangles. Have a look at the refactored function, it binds the data to a newly created group and appends the rectangle to that group: var newGroup = svg.selectAll(".group") .data(data, function(d) { return d.x }) .enter() .append("g") //etc... newGroup.append("rect") //etc... Also, use a key selection in the data binding, so you know exactly what rectangle is being dragged: .data(data, function(d){return d.x}) Here is your code with those changes: <!DOCTYPE html> <meta charset="utf-8"> <style> /*.active { stroke: #000; stroke-width: 2px; }*/ </style> <svg width="960" height="500"></svg> <script src="//d3js.org/d3.v4.min.js"></script> <script> var margin = { top: 20, right: 20, bottom: 20, left: 20 }, width = 600 - margin.left - margin.right, height = 600 - margin.top - margin.bottom; var svg = d3.select("svg"); var data = [{ x: 200 }, { x: 300 }, { x: 400 }]; var groove = svg.append("g") .attr("class", "groove_group"); groove.append("rect") .attr("x", 100) .attr("y", 150) .attr("rx", 2) .attr("ry", 2) .attr("height", 6) .attr("width", 800) .style("fill", "grey"); groove.append("rect") .attr("x", 102) .attr("y", 152) .attr("rx", 2) .attr("ry", 2) .attr("height", 2) .attr("width", 796) .style("fill", "black"); // create group var group = svg.selectAll(null) .data(data, function(d){return d.x}) .enter().append("g") .attr("class", "group") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .on("click", removeElement); group.append("rect") .attr("x", function(d) { return d.x; }) .attr("y", 100) .attr("height", 100) .attr("width", 15) .style("fill", "lightblue") .attr('id', function(d, i) { return 'handle_' + i; }) .attr("rx", 6) .attr("ry", 6) .attr("stroke-width", 2) .attr("stroke", "black"); group.append("text") .attr("x", function(d) { return d.x }) .attr("y", 100) .attr("text-anchor", "start") .style("fill", "black") .text(function(d) { return "x:" + d.x }); svg.on("click", function() { var coords = d3.mouse(this); var newData = { x: coords[0], } data.push(newData); var newGroup = svg.selectAll(".group") .data(data, function(d){return d.x}) .enter() .append("g") .attr("class", "group") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .on("click", removeElement); newGroup.append("rect") .attr("x", function(d) { return d.x; }) .attr("y", 200) .attr("height", 100) .attr("width", 15) .style("fill", "steelblue") .attr('id', function(d, i) { return 'rect_' + i; }) .attr("rx", 6) .attr("ry", 6) .attr("stroke-width", 2) .attr("stroke", "black"); }); function dragstarted(d) { d3.select(this).raise().classed("active", true); } function dragged(d) { d3.select(this).select("text") .attr("x", d.x = d3.event.x); d3.select(this).select("rect") .attr("x", d.x = d3.event.x); } function dragended(d) { d3.select(this) .classed("active", false); } function removeElement(d) { d3.event.stopPropagation(); data = data.filter(function(e) { return e != d; }); d3.select(this) .remove(); } </script>
For correctly drag-and-drop behavior, rewrite your code like this: var margin = { top: 20, right: 20, bottom: 20, left: 20 }, width = 600 - margin.left - margin.right, height = 600 - margin.top - margin.bottom; var svg = d3.select("svg"); var data = [{ x: 200 }, { x: 300 }, { x: 400 }]; var groove = svg.append("g") .attr("class", "groove_group"); groove.append("rect") .attr("x", 100) .attr("y", 150) .attr("rx", 2) .attr("ry", 2) .attr("height", 6) .attr("width", 800) .style("fill", "grey"); groove.append("rect") .attr("x", 102) .attr("y", 152) .attr("rx", 2) .attr("ry", 2) .attr("height", 2) .attr("width", 796) .style("fill", "black"); // create group var group = svg.selectAll(null) .data(data) .enter().append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .on("click", removeElement); group.append("rect") .attr("x", function(d) { return d.x; }) .attr("y", 100) .attr("height", 100) .attr("width", 15) .style("fill", "lightblue") .attr('id', function(d, i) { return 'handle_' + i; }) .attr("rx", 6) .attr("ry", 6) .attr("stroke-width", 2) .attr("stroke", "black"); group.append("text") .attr("x", function(d) { return d.x }) .attr("y", 100) .attr("text-anchor", "start") .style("fill", "black") .text(function(d) { return "x:" + d.x }); svg.on("click", function() { var coords = d3.mouse(this); var newData = { x: d3.event.x, } data.push(newData); group.selectAll("rect") .data(data) .exit() .enter() .append("rect") .attr("x", function(d) { return d.x; }) .attr("y", 200) .attr("height", 100) .attr("width", 15) .style("fill", "steelblue") .attr('id', function(d, i) { return 'rect_' + i; }) .attr("rx", 6) .attr("ry", 6) .attr("stroke-width", 2) .attr("stroke", "black"); }); function dragstarted(d) { d3.select(this).raise().classed("active", true); } function dragged(d) { d3.select(this).select("text") .attr("x", d.x = d3.event.x); d3.select(this).select("rect") .attr("x", d.x = d3.event.x); } function dragended(d) { d3.select(this) .classed("active", false); } function removeElement(d) { d3.event.stopPropagation(); data = data.filter(function(e) { return e != d; }); d3.select(this) .remove(); } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.2/d3.min.js"></script> <svg width="960" height="500"></svg> But, what the problem with adding the new element, I have no idea.
D3.js Force Direct Graph - png's in circles - making the images circular
I asked this question yesterday and got a great duplicate question answer - many thanks Gerardo. The example used images with transparent backgrounds (see Opera and Chrome image below) and it works a treat however, I would like to provide square images which fit inside the circle. I've tried the border radius in CSS and Bootstrap circular image but these don't work - probably because it is an tag not an tag. Here is the rendered tag in case that is helpful. <image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="female1.png" class="circle-image" height="40" width="40" x="-20" y="-20"></image> Would setting it up as a pattern help?
You can use a SVG pattern: var defs = svg.append("defs"); defs.append('pattern') .attr("id", "foo") .attr("width", 1) .attr("height", 1) .append("svg:image") .attr("xlink:href", "foo.jpg") .attr("width", someValue) .attr("height", someValue) .attr("y", someValue) .attr("x", someValue); Then, in your circles: .attr("fill", "url(#foo") Here is a demo: var width = 400; var height = 300; var svg = d3.select("body") .append("svg") .attr("width", width) .attr("height", height); var defs = svg.append("defs"); defs.append('pattern') .attr("id", "dog") .attr("width", 1) .attr("height", 1) .append("svg:image") .attr("xlink:href", "http://cdn2-www.dogtime.com/assets/uploads/2010/12/senior-dog-2.jpg") .attr("width", 100) .attr("height", 100) .attr("y", -20) .attr("x", -20); defs.append('pattern') .attr("id", "cat") .attr("width", 1) .attr("height", 1) .append("svg:image") .attr("xlink:href", "https://s-media-cache-ak0.pinimg.com/736x/92/9d/3d/929d3d9f76f406b5ac6020323d2d32dc.jpg") .attr("width", 120) .attr("height", 120) .attr("x", -30) .attr("y", -10); var nodes = [{id:"foo"},{id:"bar"}, {id:"baz"},{id:"barbaz"}]; var edges = [{ "source": 0, "target": 1 }, { "source": 0, "target": 2 }, { "source": 0, "target": 3 }]; var simulation = d3.forceSimulation() .force("link", d3.forceLink().distance(80)) .force("charge", d3.forceManyBody().strength(-100)) .force("center", d3.forceCenter(width / 2, height / 2)); var links = svg.selectAll("foo") .data(edges) .enter() .append("line") .style("stroke", "#ccc") .style("stroke-width", 1); var node = svg.selectAll("foo") .data(nodes) .enter() .append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); var nodeCircle = node.append("circle") .attr("r", 30) .attr("stroke", "gray") .attr("stroke-width", "2px") .attr("fill", function(d,i){ return i%2 === 0 ? "url(#dog)" : "url(#cat)" }); var texts = node.append("text") .style("fill", "black") .attr("dx", 36) .attr("dy", 8) .text(function(d) { return d.id; }); simulation.nodes(nodes); simulation.force("link") .links(edges); simulation.on("tick", function() { links.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", (d) => "translate(" + d.x + "," + d.y + ")") }); function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } <script src="https://d3js.org/d3.v4.min.js"></script>
D3.js network graph using force-directed layout and rectangles for nodes
I'm trying to modify Mike's Force-Directed Graph example to use rectangles instead of circles as nodes. Also, I want text inside the rectangle. I have rectangles showing up with text correctly, however they're not attached to the links, and they do not move. Here's a codepen link: http://codepen.io/anon/pen/gpgWaz var width = 960, height = 500; var color = d3.scale.category20(); var force = d3.layout.force() .charge(-120) .linkDistance(30) .size([width, height]); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); force .nodes(graph.nodes) .links(graph.links) .start(); var link = svg.selectAll(".link") .data(graph.links) .enter().append("line") .attr("class", "link") .style("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = svg.selectAll(".node") .data(graph.nodes) .enter() .append("g") .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) .call(force.drag); node.append("rect") .attr("class", "node") .attr("width", 100) .attr("height", 35) .style("fill", function(d) { return color(d.group); }) .style("stroke", "black") .style("stroke-width", "1px"); node.append("text") .text(function(d) { return d.name; }) .style("font-size", "12px") .attr("dy", "1em"); node.append("title") .text(function(d) { return d.name; }); 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("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); }); Update Thanks to Lars's comment, and his codepen it works now. Updates I made to my code: Add transform illustrated by Lars Changed links to connect at the center of rectangles Added rounded corners to rectangles Gave the text a slight margin indentation Changed to use window width/height Here my new codepen: http://codepen.io/anon/pen/bdgREd var width = window.innerWidth, height = window.innerHeight, nodeWidth = 100, nodeHeight = 35; var color = d3.scale.category20(); var force = d3.layout.force() .charge(-1500) .linkDistance(100) .friction(0.5) .size([width, height]); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); force .nodes(graph.nodes) .links(graph.links) .start(); var link = svg.selectAll(".link") .data(graph.links) .enter().append("line") .attr("class", "link") .style("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = svg.selectAll(".node") .data(graph.nodes) .enter() .append("g") .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) .call(force.drag); node.append("rect") .attr("class", "node") .attr("width", nodeWidth) .attr("height", nodeHeight) .attr("rx", 5) .attr("ry", 5) .style("fill", function(d) { return color(d.group); }) .style("stroke", "black") .style("stroke-width", "1px"); node.append("text") .attr("x", 5) .attr("y", 2) .text(function(d) { return d.name; }) .style("font-size", "12px") .attr("dy", "1em"); node.append("title") .text(function(d) { return d.name; }); force.on("tick", function() { link.attr("x1", function(d) { return d.source.x + (nodeWidth / 2); }) .attr("y1", function(d) { return d.source.y + (nodeHeight / 2); }) .attr("x2", function(d) { return d.target.x + (nodeWidth / 2); }) .attr("y2", function(d) { return d.target.y + (nodeHeight / 2); }); node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); });
You're setting the x and y attributes on the g elements to change their positions -- this won't do anything. You need to set the transform attribute instead, like you're doing when adding the g elements. So your tick handler function would contain node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); instead of node.attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); Complete demo here.
D3 force layout - .exit().remove() just giving back errors on tick event
I have a problem with link.exit().remove(); and node.exit().remove();. If I set it in the initializeGraph method then I get several errors with the tick function I think. So my question is how or why do I get those errors: Uncaught TypeError: undefined is not a function graph-d3.js:156initializeGraph graph-d3.js:156updateForceUsingNewNodes graph-d3.js:108createGraph graph-d3.js:18$.ajax.success ajax-stuff.js:106j jquery-2.1.1.min.js:2k.fireWith jquery-2.1.1.min.js:2x jquery-2.1.1.min.js:4n.prop.on.c jquery-2.1.1.min.js:4n.event.dispatch jquery-2.1.1.min.js:3r.handle jquery-2.1.1.min.js:3 3Error: Invalid value for <line> attribute x1="NaN" d3.min.js:1 3Error: Invalid value for <line> attribute y1="NaN" d3.min.js:1 3Error: Invalid value for <line> attribute x2="NaN" d3.min.js:1 3Error: Invalid value for <line> attribute y2="NaN" d3.min.js:1 Uncaught TypeError: Cannot read property 'attr' of undefined Here is an exerpt of the source code. Not important lines are removed: var alreadyThere = false; var nodeCircles = {}; var svg, link, node; var force = d3.layout.force(); var width = 700, height = 200; var boxIDName = "#main-rightinfo"; var currentJSON; var container; var zoom = d3.behavior.zoom() .scaleExtent([0.4, 5]); var drag = force.drag(); function createGraph(newJSON){ if (alreadyThere){ svg.remove(); nodeCircles = {}; } updateForceUsingNewNodes(generateObjects(newJSON)); alreadyThere = true; currentJSON = newJSON; force.start(); } function updateGraph(newJSON){ svg.remove(); findDuplicatesAndSetEmpty(newJSON); deleteEmptyObjectsInJSON(newJSON); currentJSON = currentJSON.concat(newJSON); updateForceUsingNewNodes(generateObjects(currentJSON)); force.start(); } //here are some methods forming the json and array... function initializeGraph(){ zoom.on("zoom", zoomed); drag.on("dragstart", dragstart); force .size([width, height]) .gravity(.1) .charge(-400) .friction(0.9) .theta(0.9) .linkStrength(0.9) .distance(55) .alpha(0.1) .on("tick", tick); svg = d3.select("#main-right") .append("svg") .attr("width", width) .attr("height", height) .call(zoom).on("dblclick.zoom", null); svg .append("svg:defs").selectAll("marker") .data(["end"]) .enter().append("svg:marker") .attr("id", String) .attr("viewBox", "0 -5 10 10") .attr("refX", 32) .attr("refY", -0.05) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto") .append("svg:path") .attr("d", "M0,-5L10,0L0,5") .attr('fill', '#359AF4'); container = svg.append("g"); link = container.append("g") .attr("class", "links") .selectAll(".link") .data(force.links()) .enter().append("line") .attr("class", "link") .attr("marker-end", "url(#end)"); node = container.append("g") .attr("class", "nodes") .selectAll(".node") .data(force.nodes()) .enter().append("g") .attr("class", "node") .on("mouseover", mouseover) .on("mouseout", mouseout) .on("click", function(d) { click(d); }) .on("dblclick", function(d) { dblclick(d); }) .on('contextmenu', function(data, index) { d3.event.preventDefault(); updateGraphByRemoveElement(data, index); }) .call(drag); node .append("circle") .attr("r", 20) .attr("cx", 0) .attr("cy", 0) .style("fill", '#eee') .style("stroke", '#fff') .style("stroke-width", '0.5px'); node .append("image") .attr("xlink:href", function(d) { if (d.class == "Person") { return "pics/node_person.svg"; } else { return "pics/node_appln.svg"; }} ) .attr("x", -20) .attr("y", -20) .attr("width", 40) .attr("height", 40) .attr("color", "white"); node .append("text") .attr("x", 20) .attr("y", 4) .style("fill", "#bbb") .text(function(d) { return d.name; }); node .append("circle") .attr("r", 7) .attr("cx", 0) .attr("cy", -16) .style("fill", '#359AF4'); node .append("text") .attr("text-anchor", "center") .attr("x", -3) .attr("y", -13) .style("fill", "white") .text(function(d) { return d.linkCount; }); function tick() { 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 + ")"; }); } } //here are some functions tick, mousedown and so on... Well as you can see the svg.remove(); from the code is not needed. But until the exit().remove() do not work, it's needed. So yeah how to handle that with the tickevent/ .exit().remove(). Thank you for any tips. PS: I used this very basic one https://gist.github.com/mbostock/1095795 and another which is very close to mine D3.js - exit() section does not remove all data and this working not working, too Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't? Whole code or let's say how I think it should run, but currently getting some errors (not identical to the code above, but just changed some lines) var alreadyThere = false; var nodeCircles = {}; var svg, link, node; var force = d3.layout.force(); var width = 700, height = 400; var boxIDName = "#main-rightinfo"; var currentJSON; var container; var zoom = d3.behavior.zoom() .scaleExtent([0.4, 5]); var drag = force.drag(); function createGraph(newJSON){ if (alreadyThere){ //svg.remove(); nodeCircles = {}; } updateForceUsingNewNodes(generateObjects(newJSON)); alreadyThere = true; currentJSON = newJSON; force.start(); } function updateGraph(newJSON){ //svg.remove(); findDuplicatesAndSetEmpty(newJSON); deleteEmptyObjectsInJSON(newJSON); currentJSON = currentJSON.concat(newJSON); updateForceUsingNewNodes(generateObjects(currentJSON)); force.start(); } function findDuplicatesAndSetEmpty(newJSON){ for (var i = 0; i < currentJSON.length; i++) { for (var o = 0; o < newJSON.length; o++) { if ((currentJSON[i].source.ID == newJSON[o].source) && (currentJSON[i].target.ID == newJSON[o].target) || (currentJSON[i].source.ID == newJSON[o].target) && (currentJSON[i].target.ID == newJSON[o].source)){ newJSON[o] = {}; } } } } function deleteEmptyObjectsInJSON(json){ for (var i = 0; i < json.length; i++) { var y = json[i].source; if (y==="null" || y===null || y==="" || typeof y === "undefined"){ json.splice(i,1); i--; } } } function updateGraphByRemoveElement(clickedNode, index){ svg.remove(); var json4Splicing = currentJSON; for (var i = 0; i < json4Splicing.length; i++) { if (json4Splicing[i].source.ID == clickedNode.ID){ json4Splicing[i] = {}; } else if (json4Splicing[i].target.ID == clickedNode.ID){ json4Splicing[i] = {}; } } deleteEmptyObjectsInJSON(json4Splicing); deleteNode(force.nodes(),clickedNode); currentJSON = json4Splicing; updateForceRemoveElement(generateObjects(currentJSON)); } function deleteNode(allNodes, clickedNode){ allNodes.forEach(function(node) { if (node == clickedNode){ force.links().forEach(function(link) { if (node.ID == link.source.ID){ link.target.linkCount--; } if (node.ID == link.target.ID){ link.source.linkCount--; } }); node.linkCount = 0; } }); } function generateObjects(json) { json.forEach(function(link) { if (typeof(link.source) == "string"){ link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass, linkCount:0}); link.source.linkCount++; } if (typeof(link.target) == "string"){ link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass, linkCount:0}); link.target.linkCount++; } }); return json; } function updateForceRemoveElement(links){ force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) ); force.links(d3.values(links)); initializeGraph(); } function updateForceUsingNewNodes(links){ force.nodes(d3.values(nodeCircles).filter(function(d){ return d.linkCount; }) ); force.links(d3.values(links)); initializeGraph(); } function initializeGraph(){ zoom.on("zoom", zoomed); drag.on("dragstart", dragstart); force .size([width, height]) .gravity(.1) .charge(-400) .friction(0.9) .theta(0.9) .linkStrength(0.9) .distance(55) .alpha(0.1) .on("tick", tick); svg = d3.select("#main-right") .append("svg") .attr("width", width) .attr("height", height) .call(zoom).on("dblclick.zoom", null); svg .append("svg:defs").selectAll("marker") .data(["end"]) .enter().append("svg:marker") .attr("id", String) .attr("viewBox", "0 -5 10 10") .attr("refX", 32) .attr("refY", -0.05) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto") .append("svg:path") .attr("d", "M0,-5L10,0L0,5") .attr('fill', '#359AF4'); container = svg.append("g"); link = container.append("g") .attr("class", "links") .selectAll(".link") .data(force.links()) .enter().append("line") .attr("class", "link") .attr("marker-end", "url(#end)"); link.exit().remove(); node = container.append("g") .attr("class", "nodes") .selectAll(".node") .data(force.nodes()) .enter().append("g") .attr("class", "node") .on("mouseover", mouseover) .on("mouseout", mouseout) .on("click", function(d) { click(d); }) .on("dblclick", function(d) { dblclick(d); }) .on('contextmenu', function(data, index) { d3.event.preventDefault(); updateGraphByRemoveElement(data, index); }) .call(drag); node .append("circle") .attr("r", 20) .attr("cx", 0) .attr("cy", 0) .style("fill", '#eee') .style("stroke", '#fff') .style("stroke-width", '0.5px'); node .append("image") .attr("xlink:href", function(d) { if (d.class == "Person") { return "pics/node_person.svg"; } else { return "pics/node_appln.svg"; }} ) .attr("x", -20) .attr("y", -20) .attr("width", 40) .attr("height", 40) .attr("color", "white"); node .append("text") .attr("x", 20) .attr("y", 4) .style("fill", "#bbb") .text(function(d) { return d.name; }); node .append("circle") .attr("r", 7) .attr("cx", 0) .attr("cy", -16) .style("fill", '#359AF4'); node .append("text") .attr("text-anchor", "center") .attr("x", -3) .attr("y", -13) .style("fill", "white") .text(function(d) { return d.linkCount; }); node.exit().remove(); } function tick() { 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 + ")"; }); } d3.selection.prototype.moveToFront = function() { return this.each(function(){ this.parentNode.appendChild(this); }); }; function zoomed() { d3.event.sourceEvent.stopPropagation(); container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } function dragstart(d) { d3.event.sourceEvent.stopPropagation(); d3.select(this).classed("fixed", d.fixed = true); } function mouseover() { d3.select(this).select("text").transition() .duration(750) .style("font-size","15px") .style("fill","black"); d3.select(this).moveToFront(); } function mouseout() { d3.select(this).select("text").transition() .duration(750) .style("font-size","10px") .style("fill","#ccc"); } function click(d) { $(boxIDName).empty(); if (d.class == "Person"){ $(boxIDName).append("<h2>Person/Company</h2>"); getNodeInfoPerson(d.ID); } if (d.class == "Appln"){ $(boxIDName).append("<h2>Application</h2>"); getNodeInfoAppln(d.ID); } } function dblclick(d) { entryClicked(d.ID+"|"+d.class,false); }