I am working with the d3.js treemap layout . In the tiles the font size is too small . How to display in a bigger font for big tiles and smaller for small tiles or display the label text at the center of each tiles. Find my code link here https://codesandbox.io/s/loving-mccarthy-wfbtg. The label can be centered and the font size can be increased for better view. Also the title contains the total which I dont want to show how to remove the count in the breadcrumb title alone.
Logic is here
createTreeChart = () => {
const width = 550;
const height = 600;
const padding = 60;
const format = d3.format(",d");
const name = d =>
d
.ancestors()
.reverse()
.map(d => d.data.name)
.join("/");
function tile(node, x0, y0, x1, y1) {
d3.treemapBinary(node, 0, 0, width, height);
for (const child of node.children) {
child.x0 = x0 + (child.x0 / width) * (x1 - x0);
child.x1 = x0 + (child.x1 / width) * (x1 - x0);
child.y0 = y0 + (child.y0 / height) * (y1 - y0);
child.y1 = y0 + (child.y1 / height) * (y1 - y0);
}
}
const treemap = data =>
d3.treemap().tile(tile)(
d3
.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value)
);
const svg = d3
.select("#chart")
.append("svg")
.attr("viewBox", [0.5, -30.5, width, height + 30])
.style("font", "10px sans-serif");
const x = d3.scaleLinear().rangeRound([0, width]);
const y = d3.scaleLinear().rangeRound([0, height]);
// const svg = d3
// .create("svg")
// .select("#chart")
// .append("svg")
// .attr("viewBox", [0.5, -30.5, width, height + 30])
// .style("font", "10px sans-serif");
let group = svg.append("g").call(render, treemap(data));
function render(group, root) {
const node = group
.selectAll("g")
.data(root.children.concat(root))
.join("g");
node
.filter(d => (d === root ? d.parent : d.children))
.attr("cursor", "pointer")
.on("click", d => (d === root ? zoomout(root) : zoomin(d)));
node.append("title").text(d => `${name(d)}\n${format(d.value)}`);
node
.append("rect")
.attr("id", d => (d.leafUid = "leaf"))
.attr("fill", d => (d === root ? "#fff" : d.children ? "#ccc" : "#ddd"))
.attr("stroke", "#fff");
node
.append("clipPath")
.attr("id", d => (d.clipUid = "clip"))
.append("use")
.attr("xlink:href", d => d.leafUid.href);
node
.append("text")
.attr("clip-path", d => d.clipUid)
.attr("font-weight", d => (d === root ? "bold" : null))
.selectAll("tspan")
.data(d =>
(d === root ? name(d) : d.data.name)
.split(/(?=[A-Z][^A-Z])/g)
.concat(format(d.value))
)
.join("tspan")
.attr("x", 3)
.attr(
"y",
(d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`
)
.attr("fill-opacity", (d, i, nodes) =>
i === nodes.length - 1 ? 0.7 : null
)
.attr("font-weight", (d, i, nodes) =>
i === nodes.length - 1 ? "normal" : null
)
.text(d => d);
group.call(position, root);
}
function position(group, root) {
group
.selectAll("g")
.attr("transform", d =>
d === root ? `translate(0,-30)` : `translate(${x(d.x0)},${y(d.y0)})`
)
.select("rect")
.attr("width", d => (d === root ? width : x(d.x1) - x(d.x0)))
.attr("height", d => (d === root ? 30 : y(d.y1) - y(d.y0)));
}
// When zooming in, draw the new nodes on top, and fade them in.
function zoomin(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = (group = svg.append("g").call(render, d));
x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);
svg
.transition()
.duration(750)
.call(t =>
group0
.transition(t)
.remove()
.call(position, d.parent)
)
.call(t =>
group1
.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d)
);
}
// When zooming out, draw the old nodes on top, and fade them out.
function zoomout(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = (group = svg.insert("g", "*").call(render, d.parent));
x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);
svg
.transition()
.duration(750)
.call(t =>
group0
.transition(t)
.remove()
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d)
)
.call(t => group1.transition(t).call(position, d.parent));
}
return svg.node();
};
Change the font-size
attribute of the text nodes. Add .attr("font-size", ...)
as the following.
node
.append("text")
.attr("clip-path", d => d.clipUid)
.attr("font-weight", d => (d === root ? "bold" : null))
.attr("font-size", d => {
if (d === root) return "1em";
const width = x(d.x1) - x(d.x0), height = y(d.y1) - y(d.y0);
return Math.max(Math.min(width/5, height/2, Math.sqrt((width*width + height*height))/10), 9)
})
.selectAll("tspan")
...
This sets the font-size
to 10% of the diagonal of each rect.
In case the text becomes too small to read, font-size
is set to the minimum of 9. The texts might become too big for rects that are vertically long, so the maximum set to one fifth of the width of the rect or half of the height of the rect, whichever is bigger.
The font-size doesn't scale correctly after zooming because x
and y
scales' domains are updated only after render
. Update the domains before calling render
in zoomin
and zoomout
functions.
function zoomin(d) {
x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);
const group0 = group.attr("pointer-events", "none");
const group1 = (group = svg.append("g").call(render, d));
...
}
function zoomout(d) {
x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);
const group0 = group.attr("pointer-events", "none");
const group1 = (group = svg.insert("g", "*").call(render, d.parent));
...
}
Breadcrumb fix. When you set the rectangles' tspan
s' x
and y
, you are also setting the x
and y
of the tspan
s in the breadcrumb with the same logic. The easiest way to solve this is simply to make the breadcrumb one tspan
. This, however, limits the styling of the breadcrumb. Instead, style the breadcrumb text separately at the end of the render function.
...
.text(d => d);
node.selectAll('text').filter(d => d === root)
.selectAll("tspan").attr("y", '1.1em').attr("x", undefined);
group.call(position, root);
And the regex for the breadcrumb text doesn't look like it's working out. It's separating the text at odd places. Change name(d).split(/(?=[A-Z][^A-Z])/g)
to name(d).split(/(?=\/)/g)
.
Text centering. There's a lot to be done. Text anchor is set to middle, and transform: translate
text boxes by half of width/height.
.attr("text-anchor", d => d === root ? null : "middle")
.attr("transform", d=> d === root ? null :
`translate(${(x(d.x1) - x(d.x0))/2}, ${(y(d.y1) - y(d.y0))/2})`)
.selectAll("tspan")
And change tspan
s' y
to center them vertically.
.attr("x", 3)
.attr(
"y",
(d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + (i - nodes.length/2) * 0.9}em`
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With