D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
matt-mcdaniel
Full window
Github gist
Exonerations and Race
Built with
blockbuilder.org
<!DOCTYPE html> <head> <meta charset="utf-8"> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> <link rel="stylesheet" type="text/css" href="main.css"> </head> <body> <h1>Mistaken Witness Identification: Race Makes a Difference</h1> <h3>Contributing Factors for Exoneration in the United States Since 1989</h3> <figure></figure> <p class="footnote">Full descriptions and data publicly available from <a href="www.law.umich.edu/special/exoneration/Pages/detaillist.aspx">The National Registry of Exonerations</a></p> <script> var data; var margin = {top: 60, right: 40, bottom: 20, left: 240}; var yOffset = 23; var barHeight = 35; var dict = { 'F/MFE': 'False or Misleading Forensic Evidence', 'MWID': 'Mistaken Witness Identification', 'FC': 'False Confession', 'P/FA': 'Perjury or False Accusation', 'OM': 'Official Misconduct', 'ILD': 'Inadequate Legal Defense' } var arr = Object.keys(dict).map(function(v) { return v; }); var colors = ['#635274', '#7BB0A8', '#d3d3d3']; var width = d3.select('figure').node().clientWidth - margin.left - margin.right; var height = d3.select('figure').node().clientHeight - margin.top - margin.bottom; var svg = d3.select("figure").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); var g = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); d3.json('exonerees.json', function(err, result) { if (err) console.warn(err); data = result; normalize(); render(); }); function normalize() { var nest = d3.nest() .key(function(d) { return d.Race; }); var nested = nest.entries(data); var totals = nested .slice(0,3) .map(function(v) { return v.values; }) .reduce(function(acc, cur) { cur.forEach(function(ex) { if (!acc.hasOwnProperty(ex['Race'])) { var obj = acc[ex['Race']] = { 'total': 0 }; arr.forEach(function(x) { obj[x] = 0; }); } Object.keys(ex) .filter(function(y) { return arr.indexOf(y) > -1; }) .forEach(function(z) { if (ex[z].length > 0) { acc[ex['Race']][z] += 1; } }); acc[ex['Race']]['total'] += 1; }); return acc; }, {}); var percentages = (function(t) { var p = {}; Object.keys(t).forEach(function(v) { p[v] = {}; Object.keys(t[v]).forEach(function(w) { if (w !== 'total') { var percent = t[v][w] / t[v]['total']; var percentRounded = Math.round(percent * 100); p[v][w] = percentRounded; } else { p[v][w] = t[v][w]; } }) }); return p; })(totals); //make array of objects that are 6 categories var formatted = (function(p) { var f = []; arr.forEach(function(v) { var obj = { 'category' : v }; Object.keys(p).forEach(function(w) { //console.info(w, v, p[w][v]); obj[w] = p[w][v]; }) f.push(obj); }) return f; })(percentages); //get total/category and adjusted percentage; var adjusted = (function(f) { var maxes = f.map(function(v) { var max = 0; var total = 0; Object.keys(v).forEach(function(w) { if (w !== 'category') { total += v[w]; } }) v['total'] = total; return v; }); var increase = maxes.map(function(x) { var obj = {}; var increaseAmt = (100 - x.total) / 3; Object.keys(x).forEach(function(y) { if (y !== 'category' && y!== 'total') { x[y] += increaseAmt; } obj[y] = x[y]; }) return obj; }) return increase; })(formatted); var sorted = adjusted.sort(function(a, b) { return a['Black'] + a['Hispanic'] > b['Black'] + b['Hispanic']; }); data = sorted; } function render() { var x = d3.scale.linear().domain([0, 100]).range([0, width]); var y = d3.scale.ordinal() .domain(data.map(function(d) { return d.category; })) .rangeBands([height, 0], 0.3, 0); var yLabel = d3.scale.ordinal() .domain(data.map(function(d) { return dict[d.category]; })) .rangeBands([height, 0], 0.3, 0); var xAxis = d3.svg.axis().scale(x).orient('top') .tickFormat(function(d) { return d + '%'; }); var yAxis = d3.svg.axis().scale(yLabel).orient('left'); g.append('g') .classed('x axis', true) .call(xAxis); g.selectAll('.x.axis') .select('text') .style('text-anchor', 'start'); g.append('g') .classed('y axis', true) .call(yAxis); g.select('.y.axis') .selectAll('.tick') .select('text') .each(function(d, i) { if (i === data.length - 1) { d3.select(this) .style('fill', '#512B52') .style('font-weight', 'bold') .attr('font-style', 'italic'); } }); g.append('text') .classed('x label', true) .attr({ x: 0, y: -margin.top/2 }) .text('Percent of Total'); var section = g.selectAll('.section') .data(data) .enter().append('g') .classed('section', true) .attr('transform', function(d) { return 'translate(0,' + y(d.category) + ')'; }); var black = section.append('rect') .classed('bar Black', true) .attr('fill', colors[0]) .attr('width', function(d) { return x(d['Black']); }) .attr('height', barHeight); var hispanic = section.append('rect') .classed('bar Hispanic', true) .attr('fill', colors[1]) .attr('x', function(d) { return x(d['Black']); }) .attr('width', function(d) { return x(d['Hispanic']); }) .attr('height', barHeight); var white = section.append('rect') .classed('bar Caucasian', true) .attr('fill', colors[2]) .attr('x', function(d) { return x(d['Black']) + x(d['Hispanic']); }) .attr('width', function(d) { return x(d['Caucasian']); }) .attr('height', barHeight); var tooltip = g.append('text') .text('0%') .attr('opacity', 0) .attr('fill', 'white'); var labels = ['Black', 'Hispanic', 'White']; g.selectAll('.key') .data(labels) .enter().append('text') .classed('key', true) .text(function(d) { return d; }) .attr('fill', 'white') .attr('text-anchor', 'middle') .attr('x', function(d, i) { var offset; var j = data.length - 1; if (i === 0) { offset = x(data[j]['Black']) / 2; } else if (i === 1) { offset = x(data[j]['Black']) + (x(data[j]['Hispanic']) / 2); } else { offset = x(data[j]['Black']) + x(data[j]['Hispanic']) + (x(data[j]['Caucasian']) / 2); } console.info(offset); return offset; }) .attr('y', yOffset); d3.selectAll('.bar') .on('mouseover', function(d) { var xLoc = +d3.select(this).attr('x') || 0; var parent = d3.select(this).node().parentNode; var arr = d3.select(parent).attr('transform').split(','); var yLoc = +arr[1].substring(0, arr[1].length - 1); var cl = d3.select(this).attr('class').split(' ')[1]; tooltip .text(Math.round(d[cl]) + '%') .attr({ x: xLoc + 8, y: yLoc + yOffset }) .transition() .attr('opacity', 1); }) .on('mouseleave', function() { tooltip.attr('opacity', 0); }); } </script> </body>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js