Related
I have the following code and I would like to add text in the circles. How could I make it possible; I have looked at these possible duplicates
Insert text inside Circle in D3 chart
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", d => Math.sqrt(d.value));
// link.append("title").text(d => d.value);
const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", d => color(d.group))
.call(drag(simulation));
node.append("title").text(d => "Node: " + d.id);
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
There are numerous ways to add text labels to circles. In your code, you've added title elements to the circles, which usually show up as tooltips when you hover over the element. Unlike title elements, text elements cannot be added as children of the circle elements. One common way to handle them is to use g elements to group together the circle and the related text, so that you can then perform any transformations (or removals, etc.) on the g instead of having to apply them separately to text and circle.
To convert your code sample, first, I've altered the selectAll / enter code to act on g elements instead of circles:
const node = svg
[...]
.selectAll('.circle')
.data(nodes)
.enter()
.append('g') // for each item in nodes, add <g class='circle'>
.classed('circle', true)
You can then append the circle and text elements to node:
node
.append("circle")
.attr('r', 5)
node
.append("text")
.text(d => "Node: " + d.id)
See the code snippet for a full example.
var nodes = [{
"x": 80,
"y": 60,
id: 'foo'
}, {
"x": 20,
"y": 70,
id: 'bar'
}, {
"x": 40,
"y": 40,
id: 'baz'
}, {
"x": 50,
"y": 90,
id: 'bop'
}];
const svg = d3.select('svg')
const node = svg
.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll(".circle")
.data(nodes)
.enter()
.append('g')
.classed('circle', true)
.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
node
.append("circle")
.attr("r", 5)
.attr("fill", 'deepskyblue')
// .call(drag(simulation));
node
.append("text")
.classed('circleText', true)
.attr('dy', '0.35em')
.attr('dx', 5)
.text(d => "Node: " + d.id);
svg .circleText {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
fill: black;
stroke-width: 0;
}
<script src="http://d3js.org/d3.v5.js"></script>
<svg width="200" height="200"></svg>
I am quite new in the world of D3. Now I would like to create a pie chart based on this
example with the exception that the first item in the list would be as a default value on the text displayed in the center. I have tried to chain the functions in different places of the code, but haven't been able to find the correct way to do it.
Thank you in advance!
Code also pasted here:
var data = [
{name: "USA", value: 40},
{name: "UK", value: 20},
{name: "Canada", value: 30},
{name: "Maxico", value: 10},
];
var text = "";
var width = 260;
var height = 260;
var thickness = 40;
var duration = 750;
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("#chart")
.append('svg')
.attr('class', 'pie')
.attr('width', width)
.attr('height', height);
var g = svg.append('g')
.attr('transform', 'translate(' + (width/2) + ',' + (height/2) + ')');
var arc = d3.arc()
.innerRadius(radius - thickness)
.outerRadius(radius);
var pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
var path = g.selectAll('path')
.data(pie(data))
.enter()
.append("g")
.on("mouseover", function(d) {
let g = d3.select(this)
.style("cursor", "pointer")
.style("fill", "black")
.append("g")
.attr("class", "text-group");
g.append("text")
.attr("class", "name-text")
.text(`${d.data.name}`)
.attr('text-anchor', 'middle')
.attr('dy', '-1.2em');
g.append("text")
.attr("class", "value-text")
.text(`${d.data.value}`)
.attr('text-anchor', 'middle')
.attr('dy', '.6em');
})
.on("mouseout", function(d) {
d3.select(this)
.style("cursor", "none")
.style("fill", color(this._current))
.select(".text-group").remove();
})
.append('path')
.attr('d', arc)
.attr('fill', (d,i) => color(i))
.on("mouseover", function(d) {
d3.select(this)
.style("cursor", "pointer")
.style("fill", "black");
})
.on("mouseout", function(d) {
d3.select(this)
.style("cursor", "none")
.style("fill", color(this._current));
})
.each(function(d, i) { this._current = i; });
g.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.text(text);
To get a default value to show up, just append the values during rendering of the chart. To update the value mouseover, just select the .text-group and update the values.
Check updated codepen
Question: Is it possible for me to make each node individually, and then use the force layout to connect them? If not, how would I go about pre-placing the nodes? And if so, can I get some help with the syntax, please?
Context: I am new to D3, and am trying to make a force-directed graph for only five nodes as part of the landing page for an academic project. I am using this example and this example, and sort of want to make a combination of the two by putting my nodes in the arrays.
For example, could I do something like:
var w = 1300;
var h = 10000;
//An area for svg elements
var svgArea = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
//All the node definitions
var nodeMain = svgArea.append("a")
.attr("height", 300)
.attr("width", 300)
.append("circle")
.attr("r", 300)
.attr("cx", 650)
.attr("cy", 700)
.attr("fill", "orange");
var nodeMedia = svgArea.append("a")
.attr("height", 200)
.attr("width", 200)
.append("circle")
.attr("r", 200)
.attr("cx", 250)
.attr("cy", 1150)
.attr("fill", "orange");
var nodeRef = svgArea.append("a")
.attr("height", 200)
.attr("width", 200)
.append("circle")
.attr("r", 200)
.attr("cx", 1050)
.attr("cy", 1150)
.attr("fill", "orange");
//Nodes for the visualization
var nodes = [nodeMain, nodeMedia, nodeRef];
//Connected using indices of the array
var edges = [{source: 1, target: 0}, {source: 2, target: 0}];
//Force-directed
var connect = d3.layout.force()
.size([w, h])
.gravity(1)
.distance(100)
.charge(-50);
connect.nodes(nodes).links(edges);
var orb = svgArea.selectAll(".node").data(nodes)
.enter().append("g")
.call(force.drag);
var link = svgArea.selectAll(".link").data(edges)
.enter()
.append("line")
.attr("class", "link");
connect.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.source.x})
.attr("y2", function(d) {return d.source.y});
orb.attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";});
});
connect.start();
(And in the event I asked a really silly question, would anyone mind directing me to some D3 resources where I can learn more of the concepts/syntax without emulating/relying purely on examples?)
Thank you in advance, everyone!
I have made slight changes in your code snippet and added necessary comments. Share your queries if any.
var w = 500;
var h = 500;
//An area for svg elements
var svgArea = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
//Nodes for the visualization
var nodes = [{
name: "Main",
x: 80,
y: 10
}, {
name: "Media",
x: 15,
y: 40
}, {
name: "Reference",
x: 60,
y: 60
}];
//Connected using indices of the array
var edges = [{
source: 1,
target: 0
}, {
source: 2,
target: 0
}];
//Force-directed
var connect = d3.layout.force()
.size([w, h])
.gravity(1)
.distance(150)
.charge(-200);
connect
.nodes(nodes)
.links(edges);
//Creating links
var link = svgArea.selectAll(".link")
.data(edges)
.enter()
.append("line")
.attr("class", "link")
.style("stroke", "black");
//Creating nodes
var orb = svgArea.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
orb.append("circle")
.attr("r", 10)
.style("fill", "orange");
//Adding Labels
orb.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name
});
//Adding images
orb.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -10)
.attr("y", -10)
.attr("width", 20)
.attr("height", 20);
orb.on("click",function(d){
alert("clicked "+d.name);
});
connect.on("tick", function() {
//Updating the link positions during force simulation.
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
});
//Updating the node position during force simulation
orb.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
connect.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
D3 has a rich documentation which is available here: GitHub
Fiddle: https://jsfiddle.net/gilsha/kv05y1hq/
If it's of any help I have the following Plunkers...
http://plnkr.co/edit/TiKKmvydqXNipe103juL?p=preview
http://plnkr.co/edit/ZSmvH05nnAD6cYZb0EM4?p=preview
The first one is to show/hide groups of elements when clicked on.
The second is to demonstrate drag/zoom.
Also the data is externalised into a json file and read in using...
d3.json("data.json", function(error, graph) {
This should enable you to reduce your node definitions down to one function.
I'm trying to create points between any 2 points of my triangle. How do i create mid-points between 2 points? For example, how would i create a point
between the coordinates (150, 0) and (0, 200)?
var points = [
{"x": 150, "y": 0},
{"x": 0, "y": 200},
{"x": 300, "y": 200},
{"x": 150, "y": 0}
];
//CREATE THE SVG CONTAINER
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500)
.style('fill', '#113F8C')
.style('padding-left', '20px')
.style('padding-top', '5px');
// DRAW THE PATH
var path = svg.append("path")
.data([lineData])
.attr("stroke", "black")
.attr("stroke-width", 3)
.attr("d", d3.line());
//SET THE POINTS
svg.selectAll("circle")
.data(points)
.enter().append("circle")
.attr("cx", function(d) {
console.log(d.x+ " hello");
for (var i = 0; i < points.length; i++) {
//console.log(points[i]);
x[i] = d.x;
console.log(d.x);
};
return d.x;
})
.attr("cy", function(d) {console.log(d.y+ " hello"); return d.y; })
.attr("r", 5);
// THE X TEST
x[0];
Since you have a points data with a redundant fourth point in the triangle (which is the same of the first), we can simply remove this fourth object of the data:
.data(points.slice(0, points.length-1))
And don't even mind about the range error of the last data:
.attr("cx", (d,i)=>(d.x + points[i+1].x)/2)
.attr("cy", (d,i)=>(d.y + points[i+1].y)/2);
Here is a demo. The blue points are the points of your data array, the red points are the mid points.
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 300);
var points = [
{"x": 150, "y": 10},
{"x": 10, "y": 200},
{"x": 300, "y": 200},
{"x": 150, "y": 10}
];
var circles = svg.selectAll(".circles")
.data(points)
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "blue")
.attr("cx", d=>d.x)
.attr("cy", d=>d.y);
var midPoints = svg.selectAll(".midPoints")
.data(points.slice(0, points.length-1))
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "brown")
.attr("cx", (d,i)=>(d.x + points[i+1].x)/2)
.attr("cy", (d,i)=>(d.y + points[i+1].y)/2);
<script src="https://d3js.org/d3.v4.min.js"></script>
The same snippet, with lines for each triangle:
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 300);
var points = [
{"x": 150, "y": 10},
{"x": 10, "y": 200},
{"x": 300, "y": 200},
{"x": 150, "y": 10}
];
var line = d3.line()
.x(d=>d.x)
.y(d=>d.y);
var lineMid = d3.line()
.x((d,i)=>(d.x + points[i+1].x)/2)
.y((d,i)=>(d.y + points[i+1].y)/2)
.curve(d3.curveLinearClosed);
var lineCircles = svg.append("path")
.attr("d", line(points))
.attr("fill", "none")
.attr("stroke", "blue")
.attr("opacity", 0.4);
var lineMidPoints = svg.append("path")
.attr("d", lineMid(points.slice(0, points.length-1)))
.attr("fill", "none")
.attr("stroke", "brown")
.attr("opacity", 0.4);
var circles = svg.selectAll(".circles")
.data(points)
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "blue")
.attr("cx", d=>d.x)
.attr("cy", d=>d.y);
var midPoints = svg.selectAll(".midPoints")
.data(points.slice(0, points.length-1))
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "brown")
.attr("cx", (d,i)=>(d.x + points[i+1].x)/2)
.attr("cy", (d,i)=>(d.y + points[i+1].y)/2);
<script src="https://d3js.org/d3.v4.min.js"></script>
I have implemented the following graph with the edges rendered with d3.svg.diagonal(). However, when I try substituting the diagonal with d3.svg.line(), it doesn't appear to pull the target and source data. What am I missing? Is there something I don't understand about d3.svg.line?
The following is the code I am referring to, followed by the full code:
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
...
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
The entire code:
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width =1500,
height = 1500,
diameter = Math.min(width, height),
radius = diameter / 2;
var balloon = d3.layout.balloon()
.size([width, height])
.value(function(d) { return d.size; })
.gap(50)
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (margin.left + radius) + "," + (margin.top + radius) + ")")
root = "flare.json";
root.y0 = height / 2;
root.x0 = width / 2;
d3.json("flare.json", function(root) {
var nodes = balloon.nodes(root),
links = balloon.links(nodes);
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
var node = svg.selectAll("g.node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
node.append("circle")
.attr("r", function(d) { return d.r; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
node.append("text")
.attr("dx", function(d) { return d.x })
.attr("dy", function(d) { return d.y })
.attr("font-size", "5px")
.attr("fill", "white")
.style("text-anchor", function(d) { return d.children ? "middle" : "middle"; })
.text(function(d) { return d.name; })
});
A comparison of how the d attribute of the svg disappears when using "line."
Question is quite dated, but since I don't see an answer and someone might face the same problem, here it is.
The reason why simple replacement of diagonal with line is not working is because d3.svg.line and d3.svg.diagonal return different results:
d3.svg.diagonal returns function that accepts datum and its index and transforms it to path using projection. In other words diagonal.projection determines how the function will get points' coordinates from supplied datum.
d3.svg.line returns function that accepts an array of points of the line and transforms it to path. Methods line.x and line.y determine how coordinates of the point retreived from the single element of supplied array
D3 SVG-Shapes reference
SVG Paths and D3.js
So you can not use result of the d3.svg.line directly in d3 selections (at least when you want to draw multiple lines).
You need to wrap it in another function like this:
var line = d3.svg.line()
.x( function(point) { return point.lx; })
.y( function(point) { return point.ly; });
function lineData(d){
// i'm assuming here that supplied datum
// is a link between 'source' and 'target'
var points = [
{lx: d.source.x, ly: d.source.y},
{lx: d.target.x, ly: d.target.y}
];
return line(points);
}
// usage:
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",lineData)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Here's working version of jsFiddle mobeets posted: jsFiddle
I had the same problem...There's a jsFiddle here.
Note that changing line to diagonal will make it work.
Perhaps encapsulating the diagonal function and editing its parameters could work for you:
var diagonal = d3.svg.diagonal();
var new_diagonal = function (obj, a, b) {
//Here you may change the reference a bit.
var nobj = {
source : {
x: obj.source.x,
y: obj.source.y
},
target : {
x: obj.target.x,
y: obj.target.y
}
}
return diagonal.apply(this, [nobj, a, b]);
}
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",new_diagonal)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Just set the d attribute of link to line:
.attr("d", line)