Rendering order in d3.js - javascript

Here is the code:
//Circle Data Set
var circleData = [
{ "cx": 20, "cy": 20, "radius": 20, "color" : "green" },
{ "cx": 70, "cy": 70, "radius": 20, "color" : "purple" }];
//Create the SVG Viewport
var svgContainer = d3.select("#svgContainer")
.attr("width",200)
.attr("height",200);
//Add the SVG Text Element to the svgContainer
var text = svgContainer.selectAll("text")
.data(circleData)
.enter()
.append("text");
var circles = svgContainer.selectAll("circle")
.data(circleData)
.enter()
.append("circle")
.attr("cx", function(d) {return d.cx})
.attr("cy", function(d) {return d.cy})
.attr("r", function(d) {return d.radius})
.attr("fill", function(d) {return d.color})
//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");
http://jsfiddle.net/kindlychung/jrsxLfpg/1/
It seems d3 always renders text first, which means the text is partly hidden behind the circles:
<svg id="svgContainer" width="200" height="200">
<text x="20" y="20" font-family="sans-serif" font-size="20px" fill="red">( 20, 20 )</text>
<text x="70" y="70" font-family="sans-serif" font-size="20px" fill="red">( 70, 70 )</text>
<circle cx="20" cy="20" r="20" fill="green"></circle>
<circle cx="70" cy="70" r="20" fill="purple"></circle></svg>
How can I adjust this?

You just need to draw your text nodes after drawing your circles.
//Circle Data Set
var circleData = [
{ "cx": 20, "cy": 20, "radius": 20, "color" : "green" },
{ "cx": 70, "cy": 70, "radius": 20, "color" : "purple" }];
//Create the SVG Viewport
var svgContainer = d3.select("#svgContainer")
.attr("width",200)
.attr("height",200);
// draw your circles and any other graphic elements first!
var circles = svgContainer.selectAll("circle")
.data(circleData)
.enter()
.append("circle")
.attr("cx", function(d) {return d.cx})
.attr("cy", function(d) {return d.cy})
.attr("r", function(d) {return d.radius})
.attr("fill", function(d) {return d.color})
// These will now be appended AFTER the circles
// When you use `append` it will add text nodes to end
// of svgContainer
var text = svgContainer.selectAll("text")
.data(circleData)
.enter()
.append("text");
// Here you are editing the pre-existing `text` nodes that you added above.
// Note that you don't use `append` here.
// Instead, you are modifying the d3 selection stored in `text`
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");
Here is an edited version of your code:
http://jsfiddle.net/jrsxLfpg/2/

Related

How to add text to svg circle in d3

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>

d3 selection append element inside another

I want to create a circle inside a g element. I can create the groups and translate the element just fine. However, I haven't figure out how to create the circle into the g elements.
With this code I'm getting the element g and circles but the circles are outside the g:
var data = [
{"x": 10, "y": 10},
{"x": 20, "y": 20},
{"x": 30, "y": 30},
];
var svg = d3.select('svg');
var t = d3.transition().duration(750);
var group = svg
.selectAll(".groups")
.data(data) // JOIN
group.exit() // EXIT
.remove();
var groups = group.enter() // ENTER
.append("g")
.attr('class', 'groups')
.merge(group)
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });
var circles = svg.selectAll('.circles')
.data(data)
.style("fill", "blue");
circles.exit()
.style("fill", "brown")
.transition(t)
.style("fill-opacity", 1e-6)
.remove();
circles.enter()
.append('circle')
.attr('r', 2.5)
.attr('class', 'circles')
.style("fill", "green")
.merge(circles)
.attr("r", (d) => d.y/5)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
How can I fix it?
Here is the snippet: https://plnkr.co/edit/pnoSGSEv7pDo28HxRifd
You need to append to the groups by selecting the groups and not the svg:
var circles = groups.selectAll('.circles')
.data(data)
.style("fill", "blue");
Here is the updated code:https://plnkr.co/edit/4WtXqUrDAdCkOYSdqori

changing color of d3.js pie chart label when hovering over slice?

I'm currently changing the color of the slice directly on hover using css, but I'd like to affect the color of the label instead. I'm having trouble figuring out how to do this using the d3 syntax, any tips would be appreciated.
Thanks!
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice")
;
arcs.append("svg:path")
.attr("fill", function(d){
console.log(d.data.label, " ", (d.endAngle - d.startAngle))
if(d.endAngle - d.startAngle > .24){
return "#00C189"
} else {
return "#E3594B"
}
})
.attr("d", arc)
.on('click', function(d){ getRSS(d.data.label); })
.on('mouseover', function(d) {
// CHANGE LABEL COLOR HERE
})
.on('mouseout', function(d){
// CHANGE LABEL COLOR BACK HERE
});
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { //set the label's origin to the center of the arc
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius*.3; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "White")
.text(function(d) { return d.data.label; })
;
Try this code:
arcs.on("mouseover", function() {
d3.select(this)
.select("text")
.style("fill", "green");
}).on("mouseout", function() {
d3.select(this)
.select("text")
.style("fill", "black");
});
Demo:
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var labelArc = d3.svg.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.population;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = [{
"age": "<5",
"population": 2704659
}, {
"age": "5-13",
"population": 4499890
}, {
"age": "14-17",
"population": 2159981
}, {
"age": "18-24",
"population": 3853788
}, {
"age": "25-44",
"population": 14106543
}, {
"age": "45-64",
"population": 8819342
}, {
"age": "≥65",
"population": 612463
}];
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.age);
});
g.append("text")
.attr("transform", function(d) {
return "translate(" + labelArc.centroid(d) + ")";
})
.attr("dy", ".35em")
.text(function(d) {
return d.data.age;
});
g.on("mouseover", function() {
d3.select(this)
.select("text")
.style("fill", "green");
}).on("mouseout", function() {
d3.select(this)
.select("text")
.style("fill", "black");
});
function type(d) {
d.population = +d.population;
return d;
}
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

d3: A sub array of objects

I have the following structure:
[
{ 'length': 10, attributes: [1,2,3] },
{ 'length': 7, attributes: [1,3,4,5] },
{ 'length': 12, attributes: [3,5,7,9,10] },
]
and I am doing the following:
x = d3.scale.linear().domain([0, maxHeight]).range([50, w]),
y = d3.scale.linear().domain([0, maxHeight]).range([h, 20]);
z = d3.scale.linear().domain([0, maxHeight]).range([0, h - 20]);
var chart = svg.selectAll("g.chart")
.data(items)
.enter()
.append("svg:g")
.attr("class", "chart");
chart.append("svg:rect")
.attr("fill", 'darkblue')
.attr("class", 'data')
.attr("x", function(d, i) { return x(i+1); })
.attr("y", function(d, i) { return bottom - z(d['length']) + 15 })
.attr("width", 4)
.attr("height", function(d, i) { return z(d['length']) - z(d['min']); })
What I would like to do is add circles on each of these rectangles which corresponds to the attributes in my structure. Basically, (for one 'item'}, I should see something like this:
<g class="chart">
<rect fill="darkblue" class="data" x="626.1538461538462" y="15" width="6" height="530"></rect>
<circle cx="626.1538461538462" cy="(y1)" r="5" style="fill: #ffff00; stroke: #808080;"></circle>
<circle cx="626.1538461538462" cy="(y2)" r="5" style="fill: #ffff00; stroke: #808080;"></circle>
<circle cx="626.1538461538462" cy="(y3)" r="5" style="fill: #ffff00; stroke: #808080;"></circle>
</g>
The only thing I can think of is looping over the attributes and adding them element by element:
for (z=0; z< 3; ++z)
{
chart.append("svg:circle")
.data(items[z]['attributes'])
.style("fill", 'yellow')
.style("stroke", "gray")
.attr("cx", function(d, i) { return x(i+1); })
.attr("cy", function(d, i)
{
console.log(d);
return bottom - 15;
})
.attr("r", 5);
}
Is there a better way to do this?
You can created a nested selection instead of looping:
chart.selectAll("svg:circle")
.data(function(item) { return item.attributes; })
.enter()
.append("svg:circle")
.style("fill", 'yellow')
.style("stroke", "gray")
.attr("cx", function(d, i) { return x(i+1); })
.attr("cy", function(d, i)
{
console.log(d);
return bottom - 15;
})
.attr("r", 5);
Example:
To keep the cx the same for each parent rect, you can pass the parent_idx through
chart.selectAll("svg:circle")
.data(function(item, parent_idx) {
return item.attributes.map(function (attr_val) {
return { attr_val: attr_val, parent_idx: parent_idx };
});
})
.enter()
.append("svg:circle")
.style("fill", 'yellow')
.style("stroke", "gray")
.attr("cx", function(d, i) { return x(d.parent_idx); })
.attr("cy", function(d, i)
{
return y(d.attr_val);
})
.attr("r", 5);
You can use nested selections. The primary selection will create the groups, each group will have a data item bound to it.
var data = [
{name: 'A', items: [1, 2]},
{name: 'B', items: [2, 3, 4]}
];
var cScale = d3.scale.category10()
.domain(d3.range(10));
var grp = svg.selectAll('g.main')
.data(data)
.enter()
.append('g')
.attr('class', 'main')
.attr('transform', function(d, i) {
return 'translate(0,' + i * 20 + ')';
});
Then, you can create a nested selection, passing an accessor function to the data method. I have an example with rect elements, but with circles is the same:
grp.selectAll('rect')
.data(function(d) { return d.items; })
.enter()
.append('rect')
.attr('x', function(d) { return 10 * d; })
.attr('width', 8)
.attr('height', 10)
.attr('fill', function(d) { return cScale(d); });
You may found the article Nested Selections useful. I wrote a small jsfiddle too: http://jsfiddle.net/pnavarrc/h2YVd/

d3 add text to circle

I am trying to add some text into circle. I have been following example from a mbostock tutorial, but wasn't able to get the right output.
The code snippet is:
var data;
var code;
d3.json("/json/trace.json", function(json) {
data = json;
console.log(data);
// get code for visualization
code = data["code"];
alert(code);
var mainSVG = d3
.select("#viz")
.append("svg")
.attr("width", 900)
.attr("height", 900);
mainSVG
.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 100)
.attr("cx", 300)
.attr("cy", 300);
circle = mainSVG.selectAll("circle").data([code]);
});
Any suggestions how to get this work?
Here is an example showing some text in circles with data from a json file: http://bl.ocks.org/4474971. Which gives the following:
The main idea behind this is to encapsulate the text and the circle in the same "div" as you would do in html to have the logo and the name of the company in the same div in a page header.
The main code is:
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
d3.json("data.json", function(json) {
/* Define the data for the circles */
var elem = svg.selectAll("g")
.data(json.nodes)
/*Create and place the "blocks" containing the circle and the text */
var elemEnter = elem.enter()
.append("g")
.attr("transform", function(d){return "translate("+d.x+",80)"})
/*Create the circle for each block */
var circle = elemEnter.append("circle")
.attr("r", function(d){return d.r} )
.attr("stroke","black")
.attr("fill", "white")
/* Create the text for each block */
elemEnter.append("text")
.attr("dx", function(d){return -20})
.text(function(d){return d.label})
})
and the json file is:
{"nodes":[
{"x":80, "r":40, "label":"Node 1"},
{"x":200, "r":60, "label":"Node 2"},
{"x":380, "r":80, "label":"Node 3"}
]}
The resulting html code shows the encapsulation you want:
<svg width="960" height="500">
<g transform="translate(80,80)">
<circle r="40" stroke="black" fill="white"></circle>
<text dx="-20">Node 1</text>
</g>
<g transform="translate(200,80)">
<circle r="60" stroke="black" fill="white"></circle>
<text dx="-20">Node 2</text>
</g>
<g transform="translate(380,80)">
<circle r="80" stroke="black" fill="white"></circle>
<text dx="-20">Node 3</text>
</g>
</svg>
jsfiddle with working code: http://jsfiddle.net/chrisJamesC/DY7r4/
Extended the example above to fit the actual requirements, where circled is filled with solid background color, then with striped pattern & after that text node is placed on the center of the circle.
var width = 960,
height = 500,
json = {
"nodes": [{
"x": 100,
"r": 20,
"label": "Node 1",
"color": "red"
}, {
"x": 200,
"r": 25,
"label": "Node 2",
"color": "blue"
}, {
"x": 300,
"r": 30,
"label": "Node 3",
"color": "green"
}]
};
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
svg.append("defs")
.append("pattern")
.attr({
"id": "stripes",
"width": "8",
"height": "8",
"fill": "red",
"patternUnits": "userSpaceOnUse",
"patternTransform": "rotate(60)"
})
.append("rect")
.attr({
"width": "4",
"height": "8",
"transform": "translate(0,0)",
"fill": "grey"
});
function plotChart(json) {
/* Define the data for the circles */
var elem = svg.selectAll("g myCircleText")
.data(json.nodes)
/*Create and place the "blocks" containing the circle and the text */
var elemEnter = elem.enter()
.append("g")
.attr("class", "node-group")
.attr("transform", function(d) {
return "translate(" + d.x + ",80)"
})
/*Create the circle for each block */
var circleInner = elemEnter.append("circle")
.attr("r", function(d) {
return d.r
})
.attr("stroke", function(d) {
return d.color;
})
.attr("fill", function(d) {
return d.color;
});
var circleOuter = elemEnter.append("circle")
.attr("r", function(d) {
return d.r
})
.attr("stroke", function(d) {
return d.color;
})
.attr("fill", "url(#stripes)");
/* Create the text for each block */
elemEnter.append("text")
.text(function(d) {
return d.label
})
.attr({
"text-anchor": "middle",
"font-size": function(d) {
return d.r / ((d.r * 10) / 100);
},
"dy": function(d) {
return d.r / ((d.r * 25) / 100);
}
});
};
plotChart(json);
.node-group {
fill: #ffffff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Output:
Below is the link to codepen also:
See the Pen D3-Circle-Pattern-Text by Manish Kumar (#mkdudeja) on CodePen.
Thanks,
Manish Kumar
Here's a way that I consider easier:
The general idea is that you want to append a text element to a circle element then play around with its "dx" and "dy" attributes until you position the text at the point in the circle that you like. In my example, I used a negative number for the dx since I wanted to have text start towards the left of the centre.
const nodes = [ {id: ABC, group: 1, level: 1}, {id:XYZ, group: 2, level: 1}, ]
const nodeElems = svg.append('g')
.selectAll('circle')
.data(nodes)
.enter().append('circle')
.attr('r',radius)
.attr('fill', getNodeColor)
const textElems = svg.append('g')
.selectAll('text')
.data(nodes)
.enter().append('text')
.text(node => node.label)
.attr('font-size',8)//font size
.attr('dx', -10)//positions text towards the left of the center of the circle
.attr('dy',4)

Categories

Resources