var format = d3.format(',d') , fill = d3.scale.category20c() , table = d3.select('#chart'); ; // { tests: [ { id, url, y } ] // , renderers: [ { date,family,version,name,grade,percent,scores,maxscore,type // , tests: [ {status, crash} or "P", "H", "F" or "FU" ] // } ] // } d3.json('svg-testsuite.json', function(json) { window.json = json; var results = [] , columns; json.renderers.map(function clean_up(renderer, col) { results.push(renderer.tests = renderer.tests.map(function(d) { var type = { P: 'pass', H: 'warn', F: 'fail', FU: 'fail' } , data = 'object' === typeof d ? d : {}; data.status = type[d.status || d]; data.column = 1 + col; return data; })); }); results = d3.transpose(results); /* [ { key: "Firefox" , values: [ { renderers here } ] } , ... the other renderers ] */ var renderers = d3.nest() .key(function(d) { return d.family.toLowerCase(); }) .entries(json.renderers) , colgroups = [ { key: "svg-tests", values:[] } ].concat ( renderers, { key: "test-notes", values: [] }) ; table.attr('cols', json.renderers.length + 2); var cg = table.selectAll('colgroup') .data(colgroups); cg.enter().append('colgroup') .attr('class', function(d) { return d.key; }) //.attr('span', function(d) { return d.values.length || undefined; }) .selectAll('col') .data(function(d) { return d.values; }) .enter().append('col') .attr('title', function(d) { return d.date; }) .text(function(d) { return d.version; }) ; var thead = table.select('thead'); table.node().appendChild(thead.node()); // needs to be after the var ths = window.ths = thead.select('tr').selectAll('th') .data([null].concat(json.renderers, null)) .enter().append('th') .attr('class', function(d) { return d && d.type; }) ; ths.append('div').text(function(d, i) { return i&&d ? d.name+' ' : 'Notes'; }) .append('span').text(function(d) { return d && d.date; }) ; function hovered_header(d) { var th = ths[0][d3.event.target.__data__.column]; if ('classList' in th) th.classList.add('hover'); else d3.select(th).classed('hover', true); } function leaving_header(d) { var th = ths[0][d3.event.target.__data__.column]; if ('classList' in th) th.classList.remove('hover'); else d3.select(th).classed('hover', false); } var tr = window.tr = table .append('tbody').attr('class', 'data') .on('mouseover', hovered_header) .on('mouseout', leaving_header) .selectAll('tr').data(json.tests) .enter().append('tr') .attr('title', function(d) { return d.note; }) ; var group; tr.append('th') .text(function(d, i) { return (1 + i) +': '; }) .classed('group', function(d) { var my_group = d.id.split('-')[0] , was_same = my_group === group; group = my_group; return !was_same; }) .append('a') .attr('href', function(d) { return d && d.url; }) .text(function(d, i) { return d.id; }) ; tr.selectAll('td') .data(function(d, i) { return results[i]; }) .enter().append('td') .attr('class', function(d) { return d.status; }) .attr('title', function(d) { return d.crash && 'Crashed!'; }) .classed('died', function(d) { return d.crash; }) ; tr.append('td') .attr('class', 'notes nowrap') .text(function(d) { return d.note; }) ; }); // Returns a flattened hierarchy containing all leaf nodes under the root. function classes(root) { var classes = []; function recurse(name, node) { if (node.children) node.children.forEach(function(child) { recurse(node.name, child); }); else classes.push({packageName: name, className: node.name, value: node.size}); } recurse(null, root); return {children: classes}; }