D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
basilesimon
Full window
Github gist
Treemap
Built with
blockbuilder.org
<!DOCTYPE html> <head> <meta charset="utf-8"> <script src="https://unpkg.com/d3"></script> <script src="https://unpkg.com/d3-jetpack"></script> <style> body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } svg { margin: 0 auto; display: flex; background-color: #f8f7f1; } text, .text { font: 15px arial; fill: white; color: white; } .d3-tip { line-height: 1; font: 15px arial; fill: white; color: white; z-index: 1000; } .legendTitle text { font: 18px Times New Roman; fill: black; } .playerInfoTitle text { font-size: 30px; font-family: Times New Roman; fill: black; } .playerInfo text { font: 15px arial; fill: #696969; &.mobile { font: 20px serif; fill: #1d1d1d; } } .playerNamesFee { font-size: 15px; fill: rgba(255,255,255, .6) } </style> </head> <body> <svg id="chart"></svg> <script> // set config object const config = { width: 700, height: 450 }; const margin = { top: 20, left: 20, right: 150 }; const width = config.width - margin.left - margin.right, height = config.height - margin.top; const svg = d3.select('#chart').at({ width: config.width, height: config.height, }); // construct an ordinal scale from our colour palette const timesColors = ['#254251', '#E0AB26', '#F37F2F', '#3292A6', '#6c3c5e']; const color = d3.scaleOrdinal(timesColors); const format = d3.format(',d'); d3.json('data.json', (err, dataset) => { if (err) { console.log(err); return; } const treemap = d3 .treemap() .tile(d3.treemapResquarify) .size([width, height]) .round(true) .paddingOuter(2) .paddingInner(1); const root = d3 .hierarchy(dataset) .eachBefore( d => (d.data.id = (d.parent ? d.parent.data.id + '.' : '') + d.data.name) ) .sum(sumBySize) .sort((a, b) => b.height - a.height || b.value - a.value); treemap(root); // One cell per player const container = svg.append('g').at({ class: 'container' }); const cell = container .selectAll('g') .data(root.leaves()) .enter() .append('g') .translate(d => [d.x0, d.y0]); cell .append('rect') .at({ id: d => d.data.id, class: d => (d.x1 - d.x0 > 120 && d.y1 - d.y0 > 40 ? 'wide' : null), width: d => d.x1 - d.x0, height: d => d.y1 - d.y0, fill: d => color(d.parent.data.id), }) .on('mouseover', function(d) { var _this = this; d3 .selectAll('rect') .transition() .duration(100) .style('opacity', function() { return this === _this ? 1.0 : 0.6; }); }) .on('mouseout', function(d) { d3.selectAll('rect').transition().duration(500).style('opacity', 1); }) .on('click', function(d) { appendPlayerInfo(this, d); }); // Player names cell .append('text') .attr('clip-path', d => 'url(#clip-' + d.data.id + ')') .append('tspan') .at({ x: 8, y: 8, dy: '.8em', class: 'playerNames', }) .text(function(d) { // Only display text if sibling <rect> element is wide enough const parentRect = this.parentNode.previousElementSibling; if (d3.select(parentRect).classed('wide')) { return d.data.name; } }); cell .append('text') .attr('clip-path', d => 'url(#clip-' + d.data.id + ')') .append('tspan') .at({ x: 8, y: 20, dy: '1.2em', class: 'playerNamesFee', }) .text(function(d) { // Only display text if sibling <rect> element is wide enough const parentRect = this.parentNode.parentNode.firstElementChild; if (d3.select(parentRect).classed('wide')) { return '£' + d.data.fee.split('.')[0] + 'm'; } }); // 💩 Manual labelling const key = [ { name: 'Europe', color: '#254251' }, { name: 'Britain', color: '#E0AB26' }, { name: 'Africa', color: '#3292A6' }, { name: 'S. America', color: '#F37F2F' }, ]; let legendConfig = { x: width + 10, y: 10, height: 20, }; // Create a legend element const legend = container .append('g') .at({ class: 'legendContainer' }) .selectAll('g') .data(key) .enter() .append('g') .at({ class: 'legend' }) .attr('transform', (d, i) => { const leftmargin = 0; const topmargin = 0; const x = leftmargin + legendConfig.x; const y = i * legendConfig.height + legendConfig.y + topmargin; return 'translate(' + x + ',' + y + ')'; }); const legendTitle = container .append('g') .at({ class: 'legendTitle' }) .attr('transform', (d, i) => { const height = 20; const x = legendConfig.x; const y = i * height + legendConfig.y; return 'translate(' + x + ',' + y + ')'; }); legendTitle.append('text').at({ x: 0, y: 20 }).text('Key'); legend .append('rect') .at({ width: 10, height: 10, }) .translate([0, 30]) .st({ fill: d => d.color, stroke: d => d.color, }); legend .append('text') .at({ x: 20, y: 40, }) .st({ color: '#666', fill: '#666' }) .text(d => d.name); const linewidth = config.width < 400 ? width - 20 : 100; const lineheight = config.width < 400 ? 90 : 110; legendTitle.append('line').at({ x1: 0, x2: linewidth, y1: 0, y2: 0, strokeWidth: 2, stroke: '#ddd', }); legendTitle.append('line').at({ x1: 0, x2: linewidth, y1: lineheight, y2: lineheight, strokeWidth: 2, stroke: '#ddd', }); // Create a legend element const titleheight = height; const titlemargin = 30; const playerInfoContainer = svg .append('g') .attr('class', 'playerInfoContainer') .selectAll('g') .data(key) .enter() .append('g') .attr('class', 'playerInfo') .translate((d, i) => { return [legendConfig.x, i * titleheight + 10]; }); const playerInfoTitle = svg .append('g') .attr('class', 'playerInfoTitle') .translate([legendConfig.x, titleheight * 0.3 + titlemargin]); playerInfoTitle.append('text').attr('x', 0).attr('y', 0); const playerInfo = svg .append('g') .attr('class', 'playerInfo') .translate([legendConfig.x, titleheight * 0.3 + titlemargin + 30]); playerInfo .append('text') .at({ class: 'playerInfo', x: 0, y: 10, }) .tspans(() => d3.wordwrap('Tap an area for more information', 20)); // Appends player info on click on a rect const appendPlayerInfo = (obj, data) => { playerInfoTitle.html(''); playerInfo.html(''); playerInfo.append('text').at({ x: 0, y: 0, class: 'playerInfo' }); playerInfoTitle .append('text') .at({ y: 0 }) .text('£' + data.data.fee.split('.')[0] + 'm'); playerInfo .append('text') .at({ y: -25 }) .tspans(() => { const { name, fromto } = data.data; return d3.wordwrap(name + ' ' + fromto, 15); }) .attr('dy', (d, i) => i + 15); }; }); const sumBySize = d => d.fee; </script> </body>
https://unpkg.com/d3
https://unpkg.com/d3-jetpack