'D3 v5 - how to set background color of text (only as wide as text)
Consider the code for some text I'm putting in some circles:
var margins = {top:20, bottom:300, left:30, right:100};
var height = 600;
var width = 1080;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
//var tsvData = d3.tsv('circle-pack-data.tsv');
//tsvData.then(function(rawData) {
/*
var data = rawData.map(function(d) {
return {id:d.id, parentId:d.parentId, size:+d.size}
});
*/
var data = [
[
{'id':'3Q18'},
{'id':'Greater China','parentId':'3Q18','size':428.7,'sign':'negative'},
{'id':'Thematic','parentId':'3Q18', 'size':111.8,'sign':'positive'},
{'id':'US','parentId':'3Q18', 'size':418.3,'sign':'positive'},
{'id':'India','parentId':'3Q18', 'size':9.39,'sign':'negative'},
{'id':'Europe','parentId':'3Q18', 'size':94.0,'sign':'positive'},
{'id':'Japan','parentId':'3Q18', 'size':0,'sign':'positive'},
{'id':'Global','parentId':'3Q18', 'size':41.9,'sign':'negative'}
],
[
{'id':'4Q18'},
{'id':'Greater China','parentId':'4Q18','size':217.8,'sign':'negative'},
{'id':'Thematic','parentId':'4Q18', 'size':100.5,'sign':'positive'},
{'id':'US','parentId':'4Q18', 'size':127.9,'sign':'negative'},
{'id':'India','parentId':'4Q18', 'size':1.5,'sign':'negative'},
{'id':'Europe','parentId':'4Q18', 'size':1.5,'sign':'positive'},
{'id':'Japan','parentId':'4Q18', 'size':0,'sign':'positive'},
{'id':'Global','parentId':'4Q18', 'size':52.8,'sign':'negative'}
],
];
var colorMap = {
'3Q18':"#d9d9d9",
'4Q18':"#d9d9d9",
'3Q19':"#d9d9d9",
'4Q19':"#d9d9d9",
'Greater China':"#003366",
'Thematic':"#366092",
'US':"#4f81b9",
'India':"#95b3d7",
'Europe':"#b8cce4",
'Japan':"#e7eef8",
'Global':"#a6a6a6"
};
var defs = svg.append('svg:defs');
var blue1x = "Fills/blue-1-crosshatch-redo.svg";
var blue2x = "Fills/blue-2-crosshatch.svg";
var blue3x = "Fills/blue-3-crosshatch.svg";
var blue4x = "Fills/blue-4-crosshatch.svg";
var blue5x = "Fills/blue-5-crosshatch.svg";
var blue6x = "Fills/blue-6-crosshatch.svg";
var grayx = "Fills/gray-2-crosshatch.svg";
defs.append("svg:pattern")
.attr("id", "blue1_hatch")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", blue1x)
.attr("width", 20)
.attr("height", 20)
.attr("x", 0)
.attr("y", 0);
defs.append("svg:pattern")
.attr("id", "blue2_hatch")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", blue2x)
.attr("width", 20)
.attr("height", 20)
.attr("x", 0)
.attr("y", 0);
defs.append("svg:pattern")
.attr("id", "blue3_hatch")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", blue3x)
.attr("width", 20)
.attr("height", 20)
.attr("x", 0)
.attr("y", 0);
defs.append("svg:pattern")
.attr("id", "blue4_hatch")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", blue4x)
.attr("width", 20)
.attr("height", 20)
.attr("x", 0)
.attr("y", 0);
defs.append("svg:pattern")
.attr("id", "blue5_hatch")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", blue5x)
.attr("width", 20)
.attr("height", 20)
.attr("x", 0)
.attr("y", 0);
defs.append("svg:pattern")
.attr("id", "blue6_hatch")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", blue6x)
.attr("width", 20)
.attr("height", 20)
.attr("x", 0)
.attr("y", 0);
defs.append("svg:pattern")
.attr("id", "gray_hatch")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", grayx)
.attr("width", 20)
.attr("height", 20)
.attr("x", 0)
.attr("y", 0);
var negativeMap = {
'3Q18':"#d9d9d9",
'4Q18':"#d9d9d9",
'3Q19':"#d9d9d9",
'4Q19':"#d9d9d9",
'Greater China':"url(#blue1_hatch)",
'Thematic':"url(#blue2_hatch)",
'US':"url(#blue3_hatch)",
'India':"url(#blue4_hatch)",
'Europe':"url(#blue5_hatch)",
'Japan':"url(#blue6_hatch)",
'Global':"url(#gray_hatch)"
};
var strokeMap = {
"Greater China":"#fff",
"Thematic":"#fff",
"US":"#fff",
'India':"#000",
'Europe':"#000",
'Japan':"#000",
'Global':"#000"
};
for (var j=0; j <(data.length); j++) {
var vData = d3.stratify()(data[j]);
var vLayout = d3.pack().size([250, 250]);
var vRoot = d3.hierarchy(vData).sum(function (d) { return d.data.size; });
var vNodes = vRoot.descendants();
vLayout(vRoot);
var thisClass = "circ"+String(j);
var vSlices = graphGroup.selectAll('.'+thisClass).data(vNodes).attr('class',thisClass).enter().append('g');
console.log(vNodes)
vSlices.append('circle')
.attr('cx', function(d, i) {
return d.x+(j*300)
})
.attr('cy', function (d) { return d.y; })
.attr('r', function (d) { return d.r; })
.style('fill', function(d) {
//console.log(d.data.id)
if (d.data.data.sign=='positive') {
return colorMap[d.data.id];
} else {
return negativeMap[d.data.id];
}
});
vSlices.append('text')
.attr('x', function(d,i) {return d.x+(j*300)})
.attr('y', function(d) {return d.y+5})
.attr('text-anchor','middle')
.style('fill', function(d) {return strokeMap[d.data.id]})
.style('background-color', function(d) {return colorMap[d.data.id]})
.text(function(d) {return d.data.data.size ? d.data.data.size : null});
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Since the fill of the circle can sometimes be a crosshatch (not a solid color), the readability of the text takes a hit. I also found that simply adding a text shadow wasn't quite enough. I'd like to go for the "nuclear" option and assign a solid background color to the text I append. I thought I could do that with the following:
vSlices.append('text')
.attr('x', function(d,i) {return d.x+(j*300)})
.attr('y', function(d) {return d.y+5})
.attr('text-anchor','middle')
.style('fill', function(d) {return strokeMap[d.data.id]})
.style('background-color', function(d) {return colorMap[d.data.id]})
.text(function(d) {return d.data.data.size ? d.data.data.size : null});
However this achieved nothing.
Question
What is the correct way to assign background color for svg text dynamically? (seeing as the background color will need to match the circle's color as per colorMap
)
Solution 1:[1]
A <rect>
can be added before the <text>
and given a background color.
const parent = vSlices.append("g").attr("class", "vSlices");
const rect = parent.append("rect")
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
const text = parent.append('text')
.attr('x', function(d,i) {return d.x+(j*300)})
.attr('y', function(d) {return d.y+5})
.style('fill', function(d) {return strokeMap[d.data.id]})
.text(function(d) {return d.data.data.size ? d.data.data.size : null});
rect.attr("width", function(d){
return this.parentNode.childNodes[1].getComputedTextLength() + 50
});
The canvas will be rendered as
<g class="vSlices" transform="translate(1020,230)">
<rect x="-10" y="-15" height="30" rx="15" ry="15" style="fill: rgb(241, 241, 241);" width="50.578125"></rect>
<text x="13" dy=".35em" text-anchor="start" style="fill-opacity: 1;">Data Size</text>
</g>
Solution 2:[2]
YOu can do it with css like this
p {
display: inline-block;
background-color: tomato;
}
It will give color only to the background of text. You can also use display: inline
but then you won't be able to do changes to element like margins or smth else.
So use display: inline-block
I hope it helped :)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Akhil Jayan |
Solution 2 | Mileta Dulovic |