Built with blockbuilder.org
xxxxxxxxxx
<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