Dragging of rectangles and graph work as intended in the given example. But I would like to bind the graph nodes to the rectangles underneath, so that moving a rectangle also moves the bound graph node and with it the graph link.
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = { "nodes": [{ "x": 170, "y": 120 }, { "x": 400, "y": 100 }], "links": [{ "source": 0, "target": 1 }] }
graph.links.forEach(function (d) {
d.source = graph.nodes[d.source];
d.target = graph.nodes[d.target];
});
var dataRects = [
{ "x": 20, "y": 50, "width": 200, "height": 100 },
{ "x": 350, "y": 75, "width": 150, "height": 50 }
]
var rects = svg.append("g")
.attr("id", function () { return "g_0" })
.attr("class", "rect")
.selectAll("rect")
.data(dataRects)
.enter()
.append("rect")
.attr("id", function (d, i) { return "box_" + i })
.attr("x", function (d) { return d.x; })
.attr("y", function (d) { return d.y; })
.attr("width", function (d) { return d.width })
.attr("height", function (d) { return d.height })
.call(d3.drag()
//.on("start", function(d) {}
.on("drag", function (d) {
const matrix = d3.select(this).node().transform.baseVal.consolidate().matrix;
const x = matrix.e + d3.event.dx;
const y = matrix.f + d3.event.dy;
d3.select(this).attr("transform", `translate(${x},${y})`);
})
.on("end", function (d) {
idOnEnd = d3.select(this).attr("id");
})
)
.attr("transform", "translate(0,0)");
var link = svg.append("g")
.attr("class", "link")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.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; });
var node = svg.append("g")
.attr("class", "node")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("id", function (d, i) { return "node_" + i; })
.attr("r", 9)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.call(d3.drag().on("drag", dragged));
function dragged(d) {
d.x = d3.event.x, d.y = d3.event.y;
d3.select(this).attr("cx", d.x).attr("cy", d.y);
link.filter(function (l) { return l.source === d; }).attr("x1", d.x).attr("y1", d.y);
link.filter(function (l) { return l.target === d; }).attr("x2", d.x).attr("y2", d.y);
}
// var nodeSelect = d3.select("#node_0")
// d3.select("#box_0").append(function(){return nodeSelect.node()});
// d3.select("#g_0").append(() => nodeSelect.node());
.svg {
border: 1px solid red;
}
.node {
stroke: #555;
fill: #F00;
stroke-width: 3px;
}
.link {
stroke: #555;
stroke-width: 3px;
}
.rect {
fill: lightgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
Tried various things that didn't work, among them making a rectangle or its group the node parent:
var nodeSelect = d3.select("#node_0")
d3.select("#box_0").append(function(){return nodeSelect.node()});
d3.select("#g_0").append(() => nodeSelect.node());
The group change is reflected in the DOM but doesn't produce the desired effect.
Wasn't lucky searching for pertinent posts.
The larger goal to create a flow chart, where links between rectangles are created by mouse clicks, and links are preserved and move along when rectangles are dragged around.
Related
I tried to implement a 3D force directed graph in d3.js by the help of the following codes. But was unable to create the 3D network by adding the z-coordinates.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
fill: blue;
stroke: black;
stroke-width: 2px;
}
.node.visited {
fill: red;
}
.link {
stroke-width: 2px;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 640;
var height = 480;
var links = [{
source: 'Rohit',
target: 'Deep'
},
{
source: 'Deep',
target: 'Deepa'
},
{
source: 'Deepa',
target: 'Rohit'
},
];
var nodes = [{
"id": 1,
"desc": "Rohit",
"x": 121.0284957885742,
"y": 116.3165512084961,
"z": 59.36788940429688
},
{
"id": 2,
"desc": "Deep",
"x": 12.10284957885742,
"y": 116.3165512084961,
"z": 5.936788940429688
},
{
"id": 3,
"desc": "Deepa",
"x": 12.10284957885742,
"y": 11.63165512084961,
"z": 5.936788940429688
}
];
//adding to nodes
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {
name: link.source
});
link.target = nodes[link.target] ||
(nodes[link.target] = {
name: link.target
});
});
//adding svg to body
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var defs = svg.append('defs');
var gradient = defs
.append('linearGradient')
.attr('id', 'svgGradient')
.attr('x1', '0%')
.attr('x2', '10%')
.attr('y1', '0%')
.attr('y2', '10%');
gradient
.append('stop')
.attr('class', 'start')
.attr('offset', '0%')
.attr('start-color', 'red')
.attr('start-opacity', 1);
gradient
.append('stop')
.attr('class', 'end')
.attr('offset', '100%')
.attr('stop-color', 'blue')
.attr('stop-opacity', 1);
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.on("tick", tick)
.linkDistance(300)
.start();
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link')
.attr('stroke', 'url(#svgGradient)');
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.on("click", clicked)
.attr('r', width * 0.03)
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('cz', function(d){
return d.z;
});
//Code for clicking func.
function clicked(event, d) {
if (event.defaultPrevented) return; // dragged
d3.select(this).transition()
.style("fill", "black")
.attr("r", width * 0.2)
.transition()
.attr("r", width * 0.03)
.transition()
.style("fill", "blue")
//.attr("fill", d3.schemeCategory10[d.index % 10]);
}
//define the tick func.
function tick(e) {
node
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('cz', function(d) {
return d.z;
})
.call(force.drag);
link
.attr('x1', function(d) {
return d.source.x;
})
.attr('y1', function(d) {
return d.source.y;
})
.attr('z1', function(d) {
return d.source.z;
})
.attr('x2', function(d) {
return d.target.x;
})
.attr('y2', function(d) {
return d.target.y;
})
.attr('z2', function(d) {
return d.source.z;
})
}
</script>
</body>
The image that came was carrying two parts one graph where the nodes are taking (x-y) coordinates assigned to them. And then I had another graph where the nodes are taking all x-y-z coordinates.
Can anyone please help us by telling how can we add the z co-ordinates to code.
I want to add text outside the circle using d3.js but only text is not getting added,other code works fine. How to add text to circle?
//create somewhere to put the force directed graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var radius = 15;
var nodes_data = [{
"name": "Bacillus",
"sex": "F"
}, {
"name": "Candida",
"sex": "M"
}, {
"name": "Dorhea",
"sex": "M"
}, {
"name": "Pichia",
"sex": "F"
},
]
//Sample links data
//type: A for Ally, E for Enemy
var links_data = [{
"source": "Candida",
"target": "Bacillus",
"type": "A"
}, {
"source": "Dorhea",
"target": "Candida",
"type": "E"
}, {
"source": "Bacillus",
"target": "Dorhea",
"type": "A"
}, {
"source": "Bacillus",
"target": "Pichia",
"type": "C"
},
]
var linkElements = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", 1)
.attr("stroke", "rgba(50, 50, 50, 0.2)")
var nodeElements = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 18)
.attr("fill", getNodeColor)
var textElements = svg.append("g")
.attr("class", "texts")
.selectAll("text")
.data(nodes)
.enter().append("text")
.text(function(node) {
return node.name
})
.attr("font-size", 20)
.attr("dx", 15)
.attr("dy", 4)
simulation.nodes(nodes).on('tick', () => {
nodeElements
.attr('cx', function(node) {
return node.x
})
.attr('cy', function(node) {
return node.y
})
textElements
.attr('x', function(node) {
return node.x
})
.attr('y', function(node) {
return node.y
})
linkElements
.attr('x1', function(link) {
return link.source.x
})
.attr('y1', function(link) {
return link.source.y
})
.attr('x2', function(link) {
return link.target.x
})
.attr('y2', function(link) {
return link.target.y
})
})
//set up the simulation
var simulation = d3.forceSimulation()
//add nodes
.nodes(nodes_data);
var link_force = d3.forceLink(links_data)
.id(function(d) {
return d.name;
});
var charge_force = d3.forceManyBody()
.strength(-2500);
var center_force = d3.forceCenter(width / 2, height / 2);
simulation
.force("charge_force", charge_force)
.force("center_force", center_force)
.force("links", link_force);
//add tick instructions:
simulation.on("tick", tickActions);
//draw lines for the links
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links_data)
.enter().append("line")
.attr("stroke-width", 2)
.style("stroke", linkColour);
//draw circles for the nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", radius)
.attr("fill", circleColour);
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node)
/** Functions **/
//Function to choose what color circle we have
//Let's return blue for males and red for females
function circleColour(d) {
if (d.sex == "M") {
return "blue";
} else {
return "pink";
}
}
//Function to choose the line colour and thickness
//If the link type is "A" return green
//If the link type is "E" return red
function linkColour(d) {
if (d.type == "A") {
return "green";
} else {
return "red";
}
}
//drag handler
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function tickActions() {
//constrains the nodes to be within a box
node
.attr("cx", function(d) {
return d.x = Math.max(radius, Math.min(width - radius, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(radius, Math.min(height - radius, 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;
});
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: black;
stroke-width: 0px;
}
svg {
border: 1px solid black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="400"></svg>
The above code adds circles with colors and links between circle but i want to add text also outside the circle but when I apply code to add text its not adding text.how to solve this problem?
The code to add text is:
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")`enter code here`
.text(function(d) { return d.name; });
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
I want to insert some text inside my Circle which is plot in bubble chart using D3.js.
I am able to draw circle in svg as per the data provided to it, but facing a problem while append text to it.
below is my code:
<script type="text/javascript">
var sampleData = [{"x": 8,"y": 1}, {"x": 2,"y": 1}, {"x": 4,"y": 1},{"x": 5,"y": 1}];
// {"x": 6,"y": 40}, {"x": 8,"y": 100}, {"x": 10,"y": 60}];
$(function() {
InitChart();
});
function InitChart() {
// Chart creation code goes here
var vis = d3.select("#svgVisualize");
var xRange = d3.scale.linear().range([40, 400]).domain([0,10]);
var yRange = d3.scale.linear().range([200, 40]).domain([0,2]);
/* var xRange = d3.scale.linear().range([40, 400]).domain([d3.min(sampleData, function(d) {
return (d.x);
}), d3.max(sampleData, function(d) {
return d.x;
})]);
var yRange = d3.scale.linear().range([400, 40]).domain([d3.min(sampleData, function(d) {
return d.y;
}), d3.max(sampleData, function(d) {
return d.y;
})]); */
var xAxis = d3.svg.axis().scale(xRange).ticks(2);
var yAxis = d3.svg.axis().scale(yRange).ticks(2).orient("left");
vis.append("svg:g").call(xAxis).attr("transform", "translate(0,200)");
vis.append("svg:g").call(yAxis).attr("transform", "translate(40,0)");
var circles = vis.selectAll("circle").data(sampleData);
circles
.enter()
.insert("circle")
.attr("cx", function(d) { return xRange (d.x); })
//.attr("cy", function(d) { return yRange (d.y); })
.attr("cy", function(d) { return yRange (d.y); })
.attr("r", function(d) { return Math.log(d.x) * 30; })
.attr("stroke","black")
.style("fill", "yellow");
var text = vis.selectAll("text")
.data(sampleData)
.enter()
.insert("text");
//Add SVG Text Element Attributes
var textLabels = text
.attr("x", function(d) { return d.cx; })
.attr("y", function(d) { return d.cy; })
.text( function (d) { return "( " + d.cx + ", " + d.cy +" )"; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "red");
}
</script>
<body>
<svg id="svgVisualize" width="500" height="250" style="border:1px solid Red;"></svg>
</body>
Can anyone suggest what is the problem with above code?
Thanks
When you do this:
var text = vis.selectAll("text")
.data(sampleData)
.enter()
.insert("text");
You are selecting <text> elements that already exist in that SVG (in your case, the axes' ticks). Because of that, your "enter" selection is empty.
Solution: select something that doesn't exist, like null:
var text = vis.selectAll(null)
.data(sampleData)
.enter()
.insert("text");
Here is the updated code:
var sampleData = [{
"x": 8,
"y": 1
}, {
"x": 2,
"y": 1
}, {
"x": 4,
"y": 1
}, {
"x": 5,
"y": 1
}];
var vis = d3.select("svg");
var xRange = d3.scale.linear().range([40, 400]).domain([0, 10]);
var yRange = d3.scale.linear().range([200, 40]).domain([0, 2]);
var xAxis = d3.svg.axis().scale(xRange).ticks(2);
var yAxis = d3.svg.axis().scale(yRange).ticks(2).orient("left");
vis.append("svg:g").call(xAxis).attr("transform", "translate(0,200)");
vis.append("svg:g").call(yAxis).attr("transform", "translate(40,0)");
var circles = vis.selectAll("circle").data(sampleData);
circles
.enter()
.append("circle")
.attr("cx", function(d) {
return xRange(d.x);
})
//.attr("cy", function(d) { return yRange (d.y); })
.attr("cy", function(d) {
return yRange(d.y);
})
.attr("r", function(d) {
return Math.log(d.x) * 30;
})
.attr("stroke", "black")
.style("fill", "yellow");
var text = vis.selectAll(null)
.data(sampleData)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) {
return xRange(d.x);
})
.attr("text-anchor", "middle")
.attr("y", function(d) {
return yRange(d.y);
})
.text(function(d) {
return "( " + d.x + ", " + d.y + " )";
})
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
line, path{
fill: none;
stroke: black;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<svg width="400" height="220"></svg>
PS: Don't use insert, use append instead.
A better way of doing this is to use svg groups, then you can position both the circles and the text together:
var join = vis.selectAll(".points").data(sampleData);
var groups = join
.enter()
.append("g")
.attr("transform", function(d) { return "translate(" + [d.x, d.y] + ")"; });
.attr("cx", function(d) { return xRange (d.x); })
groups.append("circle")
.attr("r", function(d) { return Math.log(d.x) * 30; })
.attr("stroke","black")
.style("fill", "yellow");
groups.append("text")
.data(sampleData)
.text( function (d) { return "( " + d.cx + ", " + d.cy +" )"; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "red");
I've created a simple D3 Force Layout graph. Please check it out in the JSFiddle here.
The graph is very basic - it features cities as nodes connected to nodes representing the ocuntry they are in. For simplicity, I've made only six nodes.
I've created a function called deleteNodeOnClick() and set it on the nodes like this
var nodeEnter = node.enter()
.append('g')
.attr('class', 'node')
.on("click", deleteNodeOnClick)
When you click on a node in the graph, that node gets removed from the data (actually for simplicity the first node gets removed from the data for now) however it does not get removed from the visual graph. You can look in the console and see that it is in fact removed from the data.
Why not? I am completely stumped.
The Code
var data = {
nodes: [{
name: "Canada"
}, {
name: "Montreal"
}, {
name: "Toronto"
}, {
name: "USA"
}, {
name: "New York"
}, {
name: "Los Angeles"
}],
links: [{
source: 0,
target: 1
}, {
source: 0,
target: 2
}, {
source: 3,
target: 4
}, {
source: 3,
target: 5
}, ]
};
var node;
var link;
var force;
var width = 400,
height = 400;
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
force = d3.layout.force()
.size([width, length])
.nodes(data.nodes)
.links(data.links)
.gravity(.1)
.alpha(0.01)
.charge(-400)
.friction(0.5)
.linkDistance(100)
.on('tick', forceLayoutTick);
var link = svg.selectAll(".link")
.data(data.links);
var linkEnter = link.enter()
.append('line')
.attr('class', 'link');
link.exit().remove();
node = svg.selectAll('.node')
.data(data.nodes, function(d){
return d.name;
});
node.exit().remove();
var nodeEnter = node.enter()
.append('g')
.attr('class', 'node')
.on("click", deleteNodeOnClick)
//.attr('r', 8)
//.attr('cx', function(d, i){ return (i+1)*(width/4); })
//.attr('cy', function(d, i){ return height/2; })
.call(force.drag);
nodeEnter
.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 10)
.style("fill", "purple");
nodeEnter
.append("text")
.text(function(d) { return d.name })
.attr("class", "label")
.attr("dx", 0)
.attr("dy", ".35em");
force.start();
function forceLayoutTick(){
node.attr("transform", function(d) {
// Keep in bounding box
d.x = Math.max(10, Math.min(width - 10, d.x));
d.y = Math.max(10, Math.min(height - 10, d.y));
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; });
};
function deleteNodeOnClick(d){
var dataBefore = JSON.parse(JSON.stringify(data.nodes));
// Just delete the first node, for demonstration purposes
data.nodes.splice(0, 1);
console.info("Node should be removed", dataBefore, data.nodes);
}
CSS
#graph {
width: 100%;
height: 100%;
}
#graph svg {
background-color: #CCC;
}
.link {
stroke-width: 2px;
stroke: black;
}
.node {
background-color: darkslategray;
stroke: #138;
width: 10px;
height: 10px;
stroke-width: 1.5px;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.label {
display: block;
}
In D3, changing the data doesn't automagically change the SVG (or canvas, or HTML...) elements. You have to "repaint" your dataviz.
The good news is that you have (almost) all the selections. So, just to show you the general idea, I put all the rendering code inside a draw function, which is called on click:
function deleteNodeOnClick(d){
data.nodes = data.nodes.filter(function(e){
return e.name !== d.name;
});
draw();
}
Check the demo:
var data = {
nodes: [{
name: "Canada"
}, {
name: "Montreal"
}, {
name: "Toronto"
}, {
name: "USA"
}, {
name: "New York"
}, {
name: "Los Angeles"
}],
links: [{
source: 0,
target: 1
}, {
source: 0,
target: 2
}, {
source: 3,
target: 4
}, {
source: 3,
target: 5
}, ]
};
var node;
var link;
var force;
var width = 400,
height = 400;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
draw();
function draw() {
force = d3.layout.force()
.size([width, height])
.nodes(data.nodes)
.links(data.links)
.alpha(0.01)
.charge(-400)
.friction(0.5)
.linkDistance(100)
.on('tick', forceLayoutTick);
var link = svg.selectAll(".link")
.data(data.links);
var linkEnter = link.enter()
.append('line')
.attr('class', 'link');
link.exit().remove();
node = svg.selectAll('.node')
.data(data.nodes, function(d) {
return d.name;
});
node.exit().remove();
var nodeEnter = node.enter()
.append('g')
.attr('class', 'node')
.on("click", deleteNodeOnClick)
//.attr('r', 8)
//.attr('cx', function(d, i){ return (i+1)*(width/4); })
//.attr('cy', function(d, i){ return height/2; })
.call(force.drag);
nodeEnter
.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 10)
.style("fill", "purple");
nodeEnter
.append("text")
.text(function(d) {
return d.name
})
.attr("class", "label")
.attr("dx", 0)
.attr("dy", ".35em");
force.start();
function forceLayoutTick() {
node.attr("transform", function(d) {
// Keep in bounding box
d.x = Math.max(10, Math.min(width - 10, d.x));
d.y = Math.max(10, Math.min(height - 10, d.y));
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;
});
};
};
function deleteNodeOnClick(d) {
data.nodes = data.nodes.filter(function(e) {
return e.name !== d.name;
});
draw();
}
#graph svg {
background-color: #CCC;
}
.link {
stroke-width: 2px;
stroke: black;
}
.node {
background-color: darkslategray;
stroke: #138;
width: 10px;
height: 10px;
stroke-width: 1.5px;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.label {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body></body>
Of course, as I said, that is just to give you the general idea: for instance, the click doesn't remove the links. But now you know how to put it to work.
Your function does not do anything to the graph: (just the data)
function deleteNodeOnClick(d){
var dataBefore = JSON.parse(JSON.stringify(data.nodes));
// Just delete the first node, for demonstration purposes
data.nodes.splice(0, 1);
console.info("Node should be removed", dataBefore, data.nodes);
}
re-render or remove from the graph...
Example, just to remove (specific) "dot"
function deleteNodeOnClick(d) {
var theElement = node.filter(function(da, i) {
if (i === d.index) {
console.log(da);
return true;
}
})
console.dir(theElement);
theElement.remove();
var dataBefore = JSON.parse(JSON.stringify(data.nodes));
// Just delete the first node, for demonstration purposes
data.nodes.splice(d.index, 1);
console.info("Node should be removed", dataBefore, data.nodes);
}
Note that does NOT remove the "line" connecting the OTHER node, I will leave that exercise up to you to do.
More compact version without all the logging etc.
function deleteNodeOnClick(d) {
d3.select(node[0][d.index]).remove();
data.nodes.splice(d.index, 1);
}
(added for OTHERS visiting this site)
For d3 version 4 this would be
function deleteNodeOnClick(d) {
d3.select(node._groups[0][d.index]).remove();
data.nodes.splice(d.index, 1);
}
I am working with d3 force diagrams at the moment, I am wanting to plot my child nodes around a parent node equally spaced, so for example if I have a parent node, and 4 linked child nodes, I would want each those node positioned at 90 degree intervals? Is that possible?
Here is my current force code,
app.force
.nodes(nodes)
.links(app.edges)
.on("tick", tick)
.start();
function tick(e) {
// console.log(link);
var k = 6 * e.alpha;
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; });
linkText
.attr("x", function(d) {
return ((d.source.x + d.target.x)/2);
})
.attr("y", function(d) {
return ((d.source.y + d.target.y)/2);
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node
.attr("cx", function(d) { return d.x })
.attr("cy", function(d) { return d.y })
}
app.force = d3.layout.force()
.charge(-300)
.linkDistance(85)
.size([width, height]);
//Where we will draw our visualisation
app.svg = d3.select(".visualisation").append("svg")
.attr('width', width)
.attr('height', height);
d3.layout.force() was not created with such customisation in mind. Of course you can set some parameters, but most of the positions are automatic calculated, and changing them can be very difficult (unless you create your own force function). Version 4.x is better in that matter, but not much.
In your specific case, you can set a very high (mathematically speaking, "very low", since it is negative) charge:
var force = d3.layout.force()
.charge(-3000)
But even doing that the angles are not exactly right angles, and they vary: if you click "run snippet" you can get an almost perfect cross, but if you click it again it's not that perfect the next time. And it will not work as expected if you have data with several levels of depth.
Here is a demo:
<script src="https://d3js.org/d3.v2.min.js?2.9.3"></script>
<style>
.link {
stroke: #aaa;
}
.node text {
stroke: #333;
cursor: pointer;
}
.node circle {
stroke: #fff;
stroke-width: 3px;
fill: #555;
}
</style>
<body>
<script>
var width = 400,
height = 300
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.distance(50)
.charge(-3000)
.size([width, height]);
var json = {
"nodes": [{
"name": "node1"
}, {
"name": "node2"
}, {
"name": "node3"
}, {
"name": "node4"
}, {
"name": "node5"
}],
"links": [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 0,
"target": 4
}]
};
force
.nodes(json.nodes)
.links(json.links)
.start();
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", 2);
var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 8);
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 + ")";
});
});
</script>