Approximations for fitting text into a treemap layout, for Stack Overflow question how to make sure text in treemap cells does not overflow.
Average character width data from https://www.math.utah.edu/~beebe/fonts/afm-widths.html.
xxxxxxxxxx
<html lang="en">
<head>
<title>Treemap text fitting</title>
<meta charset="UTF-8">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<style>
.newcell text {
font-family: Arial, sans-serif;
font-size: 10px;
}
label {
display: inline-block;
padding-left: 5px;
}
ul {
list-style-type: none;
}
</style>
</head>
<body>
<div class="container">
<h2>Fitting text into cells of a treemap</h2>
<div id="controls"></div>
<div id="test"></div>
</div>
<script type="text/javascript">
d3.json('data.json', function(error, json) {
if (error) return console.warn(error);
visualizeit(json);
});
function visualizeit(data) {
var id = '#test';
var coordinates = { x: 0, y: 0 };
var margin = { top: 40, right: 10, bottom: 90, left: 20 }
var cfg = {
margin: { top: 40, right: 10, bottom: 90, left: 140 },
width: 960 - margin.left - margin.right,
height: 500 - margin.top - margin.bottom,
color: d3.scale.category20()
};
//Put all of the options into a variable called cfg
if ('undefined' !== typeof options) {
for (var i in options) {
if ('undefined' !== typeof options[i]) { cfg[i] = options[i]; }
}//for i
}
var treemap,
legendCategories,
uniqueCategories;
var half = cfg.height / 2;
var tool = d3.select("body").append("div").attr("class", "toolTip");
/////////////////////////////////////////////////////////
//////////// Create the container SVG and g /////////////
/////////////////////////////////////////////////////////
//Remove whatever chart with the same id/class was present before
d3.select(id).select("svg").remove();
//Initiate the radar chart SVG
var canvas = d3
.select(id)
.append("svg")
.attr("class", "chart")
.attr("width", cfg.width + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.height + cfg.margin.top + cfg.margin.bottom)
.attr("id", "canvas");
var innercanvas = canvas
.append("g")
.attr("class", "innercanvas")
.attr("transform", "translate(" + cfg.margin.left + "," + cfg.margin.top + ")");
legendCategories = data.children.map(a => a.name);
uniqueCategories = legendCategories.filter(onlyUnique);
treemap = d3.layout
.treemap()
.round(false)
.size([cfg.width, cfg.height])
.padding(.25)
.sticky(true)
.nodes(data);
var cells = innercanvas
.selectAll(".newcell")
.data(treemap)
.enter()
.append("g")
.attr("class", function (d,i) {
return 'newcell' + ' _' + i
+ ' cell-level-' + d.depth +
( ! d.children
? ' leaf'
: (d.depth === 0
? ' root'
: ' internal') );
})
cells
.append("rect")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
})
.attr("width", function (d) {
return d.dx;
})
.attr("height", function (d) {
return d.dy;
})
.style("fill", function (d) {
return d.children ? cfg.color(d.name) : 'none';
})
.attr("stroke", "#000000")
.attr('pointer-events', 'all')
.on("mousemove", function (d) {
tool.style("left", d3.event.pageX + 10 + "px")
tool.style("top", d3.event.pageY - 20 + "px")
tool.style("display", "inline-block");
tool.html(d.children ? null : d.name + "<br>" + d.value);
}).on("mouseout", function (d) {
tool.style("display", "none");
})
cells
.append('title')
.text(function(d){ return d.name });
cells
.append("text")
.attr("x", function (d) {
return d.x + d.dx / 2;
})
.attr("y", function (d) {
return d.y + d.dy / 2;
})
.attr('dy', '.35em')
.attr("text-anchor", "middle")
.text(function (d) {
return d.children ? '' : d.name;
})
addText(cells);
// add in some controls
var types = [
{ name: 'simple', text: 'simple minimum height and width filter' },
{ name: 'averages', text: 'filter by average character width' },
{ name: 'bbox', text: 'using getBBox' },
{ name: 'h_pad_v_pad', text: 'adding horizontal and vertical padding' },
// { name: 'complex', text: 'combining all methods' }
];
var controls = d3.select('#controls')
.append('ul')
.classed('control-group', true)
.on('change', onChange);
var options = controls.selectAll('li')
.data(types)
.enter()
.append('li');
options
.append('input')
.attr('type', 'checkbox')
.attr('value', function (d) { return d.name })
.attr('name', 'switcher')
.attr('id', function (d, i) {
return 'input_' + i;
});
options
.append('label')
.attr('for', function (d, i) {
return 'input_' + i;
})
.text( function(d) { return d.text } );
function onChange (d, i) {
var values = [],
target = d3.select(d3.event.target),
selected = d3.select(this).selectAll('input')
.filter(function (o) {
if (o) {
return this.checked;
}
})
.each(function (option) {
values.push( option.name );
});
addText(cells, values);
}
function addText( selection, values ) {
var minlength = d3.min(legendCategories);
font_size = 10,
pt_px = 0.75,
v_pad = 0, // vertical padding
h_pad = 0, // horizontal padding
averageLetterWidth = 0.58344,
minHeight = font_size * pt_px,
minWidth = minlength * averageLetterWidth * font_size * pt_px,
filters = {
simple: false,
bbox: false,
h_pad_v_pad: false,
averages: false
};
if ( arguments.length === 2 ) {
values.forEach( function(type) {
filters[type] = true;
if ( type === 'h_pad_v_pad' ) {
v_pad = 2;
h_pad = 4;
}
});
}
var t = d3.transition().duration(250);
selection.selectAll('text')
.style('opacity', 0);
selection.selectAll('text')
.transition(t)
.style('opacity', function(d) {
if ( filters.simple ) {
if ( d.dx <= minWidth + h_pad || d.dy <= minHeight + v_pad ) {
return 0
};
}
if ( filters.averages ) {
if ( ! d.name ) {
return 0;
}
if ( d.name.length * averageLetterWidth * pt_px * font_size + h_pad >= d.dx ) {
return 0
}
else if ( d.name.length * pt_px * font_size + h_pad < d.dx ) {
return 1;
}
}
if ( filters.bbox ) {
var bbox = this.getBBox();
if ( d.dx <= bbox.width + h_pad || d.dy <= bbox.height + v_pad ) {
return 0;
}
}
return 1;
});
}
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
}
</script>
</body>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js