'use strict'; var x_range = [1, 32], y_range = [0, x_range[1]]; var margin = {top: 30, bottom: 60, left:110, right: 10}, base_width = 960, base_height = 470, width = base_width - margin.left - margin.right, height = base_height - margin.top - margin.bottom; var x = d3.scale.log() .domain(x_range) .range([margin.left, margin.left + width]); var y = d3.scale.linear() .domain(y_range) .range([height + margin.top, margin.top]); var y2 = d3.scale.linear() .domain([0,1]) .range([height + margin.top, margin.top]); var x_axis = d3.svg.axis() .scale(x) .tickValues([1,2,3,4,5,6,7,8,10,12,14,16,18,20,22,24,26,28,30]) .tickFormat(d3.format(',.0f')) .orient('bottom'); var y_axis = d3.svg.axis() .scale(y) .tickValues([0,1,2,4,8,16]) .tickFormat(tickFmtStandard) .tickSize(-width,0,6) .orient('left'); var palette = ["#393b79", "#b5cf6b", "#843c39", "#de9ed6", "#8ca252", "#bd9e39", "#aecb8c", "#8c6d31", "#e7ba52", "#e7cb94", "#6b6ecf", "#ce6dbd", "#ad494a", "#5254a3", "#637939", "#d6616b", "#7b4173", "#e7969c", "#a55194", "#9c9ede"]; var color = d3.scale.ordinal().range(palette); var data = []; for (var i = x_range[0]; i < x_range[1]; i++) { for (var j = y_range[0]; j <= i; j++) { data.push([i, j]); } } var data_borders = []; data.forEach(function(v) { data_borders.push([i,j,'l']); data_borders.push([i,j,'t']); data_borders.push([i,j,'r']); data_borders.push([i,j,'b']); }); function getBin(x, y) { var b = Math.floor(Math.log(x)/Math.log(2)); var a = 0; if (y === 0) { a = 0; } else if (y <= x/2) { a = Math.floor(Math.log(y)/Math.log(2) + 1); } else if (y < x) { a = -Math.floor(Math.log(x - y)/Math.log(2) + 1); } else { a = -0.1; } return [a, b]; //return '(' + a + ', ' + b + ')'; } function getBinStr(x, y) { var bin = getBin(x, y); return '(' + bin[0] + ', ' + bin[1] + ')'; } function getBinCls(x, y) { var bin = getBin(x, y); return 'B' + (bin[0] * 10) + 'X' + bin[1]; } var chart = d3.select('#binning') .append('svg') .attr('width', base_width) .attr('height', base_height); chart.append('svg:rect') .attr('x', 0) .attr('y', 0) .attr('width', base_width) .attr('height', base_height) .attr('fill', '#ddd'); var gx = chart.append('g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + y(0) + ')') .call(x_axis); gx.selectAll('text') .attr('x', function(d) {return (x(d + 1) - x(d)) / 2;}); var axis_title_x = (x(x_range[1]) + x(x_range[0])) / 2; gx.append('g').append('text') .attr('x', function() {return axis_title_x;}) .attr('y', function() {return 40;}) .attr('text-anchor', 'middle') .text('r (number of cells in row)'); var gy_base = chart.append('g') .attr('transform', 'translate(' + x(1) + ',0)'); var gy = gy_base.append('g') .attr('class', 'y axis') .call(y_axis); gy.selectAll('text') .attr('y', function(d) {return (y(d + 1) - y(d)) / 2;}); gy_base.append('g') .attr('transform', 'translate(-70,' + ((y(y_range[1]) + y(y_range[0])) / 2) + ')') .append('text') .attr('text-anchor', 'middle') .attr('transform', 'rotate(-90)') .text('c (number of cells that share a given attribute)'); gy.selectAll('.domain') .attr('fill', 'none') .attr('stroke', 'black') .attr('stroke-width', 1); var bin = chart.selectAll('rect.bin').data(data) .enter().append('svg:rect') .attr('x', function(d, index) {return x(d[0]);}) .attr('y', function(d, index) {return y(d[1] + 1);}) .attr('width', function(d, index) {return x(d[0]+1) - x(d[0]) - 1;}) .attr('height', function(d, index) {return - y(d[1] + 1) + y(d[1]) - 1;}) .attr('class', function(d) {return getBinCls(d[0], d[1]);}) .classed('bin', true) .attr('fill', function(d, index) {return color(getBinStr(d[0], d[1]));}) //'#bfb') .on('mouseover', highlightBin) .on('mouseout', unhighlightBin) .append('svg:title') .text(function(d) {return 'c = '+d[1]+', r = ' + d[0];}); function highlightBin(d, i) { var my_bin = getBinCls(d[0], d[1]); var new_color = d3.rgb(color(getBinStr(d[0], d[1]))).brighter(); chart.selectAll('rect.' + my_bin) .attr('stroke', 'black') .attr('stroke-width', 1) .attr('fill', new_color); } function unhighlightBin(d, i) { var my_bin = getBinCls(d[0], d[1]); var orig_color = d3.rgb(color(getBinStr(d[0], d[1]))); chart.selectAll('rect.' + my_bin) .attr('stroke', 'none') .attr('fill', orig_color); } d3.select('#standard-btn').on('click', function() { chart.selectAll('rect.bin') .transition() .duration(1000) .attr('y', function(d, index) {return y(d[1] + 1);}) .attr('height', function(d, index) {return - y(d[1] + 1) + y(d[1]) - 1;}); y_axis .tickValues([0,1,2,4,8,16]) .tickFormat(tickFmtStandard); gy.call(y_axis); gy.selectAll('text') .attr('y', function(d) {return (y(d + 1) - y(d)) / 2;}); gy.selectAll('line') .attr('y1', null) .attr('y2', null) .attr('display', null); }); d3.select('#symmetric-btn').on('click', function() { chart.selectAll('rect.bin') .transition() .duration(1000) .attr('y', function(d, index) {return y((d[1] <= d[0] / 2 ? d[1] + 1: x_range[1] - (d[0] - d[1])));}) .attr('height', function(d, index) {return - y(d[1] + 1) + y(d[1]) - 1;}); y_axis .tickValues([0,1,2,4,8,15,16,23,27,29,30,31]) .tickFormat(tickFmtSymmetric); gy.call(y_axis); gy.selectAll('text') .attr('y', function(d) {return (y(d + 1) - y(d)) / 2;}); gy.selectAll('line') .attr('y1', function(d) {return (d >= y_range[1]/2 - 1) ? y(d+1)-y(d) : 0}) .attr('y2', function(d) {return (d >= y_range[1]/2 - 1) ? y(d+1)-y(d) : 0}) .attr('display', function(d) {return d === y_range[1]/2 ? 'none' : null}); }); d3.select('#stretched-btn').on('click', function() { chart.selectAll('rect.bin') .transition() .duration(1000) .attr('y', function(d, index) {return y2((d[1]+1)/(d[0]+1));}) .attr('height', function(d, index) {return y2(d[1]/(d[0]+1)) - y2((d[1]+1)/(d[0]+1)) - 1;}); y_axis .tickValues([0,15,16,31]) .tickFormat(tickFmtSymmetric); gy.call(y_axis) .selectAll('text') .attr('y', function(d) {return (y(d+1) - y(d)) / 2;}); gy.selectAll('line') .attr('y1', function(d) {return (d >= y_range[1]/2 - 1) ? y(d+1)-y(d) : 0}) .attr('y2', function(d) {return (d >= y_range[1]/2 - 1) ? y(d+1)-y(d) : 0}) .attr('display', function(d) {return d === y_range[1]/2 ? 'none' : null}); }); function tickFmtStandard(v) { if (v === y_range[0] || v === y_range[1] - 1) { return 'c = ' + v; } else { return '' + v; } } function tickFmtSymmetric(v) { if (v === y_range[0]) { return 'c = ' + v; } else if (v === y_range[1] - 1) { return 'c = r'; } else if (v === (y_range[1] / 2)) { return 'c > r/2'; } else if (v === (y_range[1] / 2 - 1)) { return 'c ≤ r/2'; } else if (v > (y_range[1] / 2 - 1)) { return 'r - ' + (y_range[1] - 1 - v); } else { return '' + v; } } function tickFmtStretched(v) { }