This block provides a rough re-creation of the Florida Gun Deaths visualization. Keeping in mind the inspiration and issues discussed, try to redesign this visualization. Focus first on editing the style (colors, thickness of lines, etc.) before changing the encoding or visualization technique.
The goal of this exercise is to illustrate how small design choices can have a large impact on how we perceive the data, as well as how hard it is to come up with a visualization that is effective, accurate, and impactful.
The original visualization appeared in the article This Chart Shows An Alarming Rise In Florida Gun Deaths After 'Stand Your Ground' Was Enacted article by Pamela Engel on Business Insider on February 18, 2014.
:warning: The author of the original chart is no longer on Twitter, so the following links no longer work. An archive of the original discussion can be found on the Wayback Machine for reference. They are kept here for reference:
The chart was created by Christine Chen. She responded to the discussion, and linked to her inspiration for the graphic.
The inspiration quoted appeared in the infographic Iraq's bloody toll by Simon Scarr on South China Morning Post on December 17, 2011.
There was a lot of discussion, especially on Twitter and various blogs, when this visualization came out. Here are some of those posts:
You can access the original (and updated) data for this visualization from the Florida Statistical Analysis Center. Specifically, you can download the "Murder: Firearm Type and Rate for Florida" dataset in PDF or Excel format the UCR Offense Data page.
To generate the data.csv
file, the original 1971_fwd_murder_firearms.xlsx
file was transformed using the following Trifacta Wrangler recipe:
header sourcerownumber: 3
delete row: IN($sourcerownumber, [1,2,4])
delete row: IN($sourcerownumber, [53, 54, 55, 56, 57, 58])
textformat col: Year type: removesymbols
replacemismatched col: Year~{Population % Change} type: Float with: null text: ''
derive type: single value: {Total by Firearm} / (Population / 100000) as: 'Murder by Firearm Rate per 100,000'
xxxxxxxxxx
<meta charset="utf-8">
<link href="https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,700" rel="stylesheet" type="text/css">
<link href="style.css" rel="stylesheet" type="text/css">
<script src="https://d3js.org/d3.v5.min.js"></script>
<body>
<!--
if there is an order you prefer for your elements, consider pre-defining the
g elements in your preferred order
can also hard-code anything not based on the data itself (see the header/footer)
//-->
<svg width="600" height="750">
<!-- axis lines should be in back -->
<g id="x"></g> <!-- x axis -->
<g id="y"></g> <!-- y axis -->
<!-- header (title and subtitle) -->
<g id="header">
<text class="title" x="0" y="0" dx="5px" dy="36pt">
Gun deaths in Florida
</text>
<text class="subtitle" x="0" y="0" dx="5px" dy="58pt">
Number of murders committed using firearms
</text>
</g>
<!-- footer (source and inspiration) -->
<g id="footer">
<text x="0" y="750" dx="5px" dy="-22pt">
Source: Florida Department of Law Enforcement
</text>
<!-- there are links in svg too: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a -->
<a href="https://tinyurl.com/theex4x">
<text x="0" y="750" dx="5px" dy="-6pt">
Inspiration: https://tinyurl.com/theex4x
</text>
</a>
</g>
<!-- actual visualization on top -->
<g id="plot">
<g id="area"></g> <!-- area chart -->
<g id="note"></g> <!-- area annotation -->
<g id="line"></g> <!-- line chart -->
<g id="points"></g> <!-- line markers -->
<g id="bars"></g> <!-- heavy black axis lines -->
</g>
</svg>
<script>
// setup svg
const svg = d3.select('body').select('svg');
// get current hard-coded size of svg
const size = {
width: parseInt(svg.attr('width')),
height: parseInt(svg.attr('height'))
};
// set padding
const pad = {top: 100, right: 45, bottom: 75, left: 45};
// setup plot area
const plot = svg.select('g#plot');
plot.attr("transform", translate(pad.left, pad.top));
// setup plot width and height
const width = size.width - pad.left - pad.right;
const height = size.height - pad.top - pad.bottom;
// setup scales and ranges
const x = d3.scaleLinear().range([0, width]);
const y = d3.scaleLinear().range([0, height]);
d3.csv("data.csv", convert).then(draw);
function draw(data) {
// filter out rows by year
const filtered = data.filter(row => row.Year >= 1989 && row.Year < 2017);
console.table(filtered);
// setup our scale ranges now that we have data
x.domain(d3.extent(filtered, row => row.Year));
y.domain([0, 1000]);
// since our g elements are already in the proper order, we can draw our svg
// elements in any order we want
drawArea(filtered);
drawLine(filtered);
drawAxis(filtered);
drawNote(filtered);
}
/*
* converts data into easier to use format
*/
function convert(input) {
let output = {};
output.Year = +input['Year'];
output.Population = +input['Population'];
output.Total = +input['Total by Firearm'];
output.Rate = +input['Murder by Firearm Rate per 100,000'];
return output;
}
/*
* draws the red area
*/
function drawArea(data) {
// svg area paths are time-consuming to create by hand
// use the area generator in d3 to do it for us
const area = d3.area()
.curve(d3.curveLinear)
.x(row => x(row.Year))
.y1(0)
.y0(row => y(row.Total));
// we do not want to create one area per element so do not data join here
// difference between calling .data(...) and .datum(...)
// https://stackoverflow.com/questions/13728402/what-is-the-difference-d3-datum-vs-data
plot.select('g#area')
.append('path')
.datum(data)
.attr('d', area);
}
/*
* draws the line and points
*/
function drawLine(data) {
// drawing the line is similar to the area
const line = d3.line()
.curve(d3.curveLinear)
.x(row => x(row.Year))
.y(row => y(row.Total));
plot.select('g#line')
.append('path')
.datum(data)
.attr('d', line);
// drawing circles uses traditional data joins
plot.select('g#points')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', row => x(row.Year))
.attr('cy', row => y(row.Total))
.attr('r', 6);
}
/*
* draws the axis lines
*/
function drawAxis(data) {
// setup x-axis to match inspiration
const xaxis = d3.axisBottom(x)
.tickSize(20, 0)
.tickValues([1990, 2000, 2010])
.tickFormat(year => String(year) + "s");
// draw x-axis
const xgroup = svg.select('g#x')
.attr('transform', translate(pad.left, pad.top + height))
.call(xaxis);
// shift each text label
xgroup.selectAll('.tick text')
.style('text-anchor', 'start')
.attr('x', 6)
.attr('y', 6);
// setup y-axis and y-axis grid lines
const yaxis = d3.axisLeft(y)
.ticks(6)
.tickSizeInner(-width);
// draw y-axis
svg.select('g#y')
.attr('transform', translate(pad.left, pad.top))
.call(yaxis);
// draw heavy axis lines
plot.select('g#bars').selectAll('line')
.data(y.range()) // one line for y-min and y-max
.enter()
.append('line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', value => value)
.attr('y2', value => value);
}
/*
* draws annotation on top of area
*/
function drawNote(data) {
const note = plot.select('g#note');
// have to manually draw each line of annotation as separate text elements
note.selectAll("text")
// text with manual line breaks
.data(['2005', 'Florida enacted', 'its "Stand Your', 'Ground" law'].reverse())
.enter()
.append('text')
.attr('id', function(value, index) { return 'note-' + index; })
.attr('x', x(2005))
.attr('y', y(450))
.attr('dx', x(2004) - x(2005))
.attr('dy', function(value, index) { return (-index * 2) + 'ex'; })
.text(value => value);
note.append('line')
.attr('x1', x(2005))
.attr('x2', x(2005))
.attr('y1', y(450) + 5)
.attr('y2', y(521));
}
/*
* creates a svg translate string
*/
function translate(x, y) {
return "translate(" + String(x) + "," + String(y) + ")";
}
</script>
</body>
</html>
https://d3js.org/d3.v5.min.js