by Adrian Liaw
This data visualisation shows how the numbers of cancellations due to different reasons vary over months, and the comparisons of them between airports.
The airports are the top 5 busiest airports in terms of total numbers of domestic flights.
The datasets were originally from RITA, but downloaded from stat-computing.org.
I tried various chart types with R and ggplot2. In this visualisation, I used line plot as my main chart type because it can effectively demonstrate the trend of numbers between months.
I also tried grouped bar chart, I thought it will be pretty efficient, but it turns out that the Month axis makes it messy because of the tightness between groups.
In my visualisation, I used x-axis to encode time (month), y-axis to encode number of cancellations, and colours for cancellation reasons.
The visualisation's layout is a 2-columned grid layout. The cell in the top-left corner has title and legend. For the rest, they are all small line charts for each airport, i.e. small multiples. On top of each chart, there's a title showing the airport's IATA code, city and State where the airport is in.
In the initial version, the main body of the visualisation is a big line chart, it has a title on its top, legends on its bottom. At the bottom of the page, there are 5 buttons for top 5 busiest airports.
There are several interaction functions that allows you to interact with the chart.
Hover onto the line charts, a tooltip would pop up and show the statistics of that month.
Hover onto the legend items, the charts will simultaneously highlight the specified line.
By clicking on the legend items, the charts will hide or show the selected line.
In the initial version, the interactions are pretty much the same, but there's only one chart, users check out different airports by clicking on the airport buttons.
After playing around with the chart, I noticed that the trend varies significantly between different airports, the visualisation really told this story. However, I think that the y-axis could be misleading because it dynamically rescales according to the selection of airport. Another thing I would like to see is some similar summary statistics about the full dataset.
This visualisation is supposed to tell users about comparisons of trends between different airports, so I think that instead of using buttons to allow users navigating between airports, you should put them side by side, use techniques like small multiples, something like facet_wrap in ggplot2. Also, it would be nice if I can explore the data on my own by cross-comparisons between months, airports and causes. Finally, I think that it would be more appropriate if the visualisation shows the percentages or ratios instead of actual numbers.
When I first saw this visualisation, I didn't even notice that I can click on the buttons to select the airport, perhaps you have to make it looks "more like a button", and probably emphasise it, because it's something very important in your visualisation. I would also like to see some information about airports, like the State of the airport, etc.
"use strict";
// I use ES6 features a lot, should work in latest Chrome and Firefox
class PlaneVisualisation {
constructor() {
this.margin = 75;
this.width = 960 - this.margin;
this.height = 900 - this.margin;
}
initSvg() {
this.container = d3.select("body")
.append("div")
.attr("id", "container");
this.svgContainer = this.container
.append("div")
.attr("id", "svg-container");
}
initAirports(airports) {
this.airports = _.keyBy(airports, "iata");
// TITLE is a placeholder for the top-left cell.
this.airports.TITLE = {};
}
initLinePlot(data) {
// TITLE placeholder data point first
this.cancels = [
{ Origin: "TITLE",
Month: 1,
CarrierCancel: 0,
WeatherCancel: 0,
NASCancel: 0,
SecurityCancel: 0 },
...data,
];
// Create rows data for the charts
this.rowsByOrigin = d3.nest()
.key(d => d.Origin)
.rollup(d => [
["x", "Carrier", "Weather", "National Aviation System", "Security"],
...d.map(r => [
// This 2008 actually doesn't matter
`2008-${r.Month}-01`,
r.CarrierCancel,
r.WeatherCancel,
r.NASCancel,
r.SecurityCancel,
]),
])
.entries(this.cancels);
this.initSmallMultiples();
}
initSmallMultiples() {
this.charts = {};
this.rowsByOrigin.forEach(({ key, values }, i) => {
// In order to put charts in a grid layout, we have to
// wrap a container that contains the actual svg, into
// another container, otherwise the style will be disabled by C3
const containerContainer = this.svgContainer
.append("div")
.attr("id", `container-${key}`)
.style("position", "absolute")
.style("top", `${30 + 250 * Math.floor(i / 2)}px`)
.style("left", `${400 * (i % 2)}px`);
const container = containerContainer.append("div");
if (key === "TITLE") {
containerContainer.append("h3")
.attr("class", "title")
.html(`US Domestic Flights 2005-2008, <br />
<small>Cancellations by Causes, Months and Airports</small>`);
containerContainer.append("span")
.attr("class", "legend-title")
.text("Causes:");
}
const airport = this.airports[key];
this.charts[key] = c3.generate({
bindto: container,
size: {
width: 400,
height: 250,
},
data: {
rows: values,
x: "x",
},
axis: {
x: {
type: "timeseries",
tick: {
// Abbreviated month name
format: "%b",
},
},
y: {
max: 4000,
label: {
// Only show the label in the first chart
text: i === 1 ? "N Cancellations" : null,
position: "outer-middle",
},
},
},
legend: {
// Only show legend in the TITLE cell
hide: i !== 0,
position: "inset",
inset: {
anchor: "bottom-right",
x: 20,
y: 50,
step: 2,
},
// When click or hover on the legend items, should action on all the charts
item: {
onclick: id => {
_.values(this.charts).forEach(c => c.toggle(id));
},
onmouseover: id => {
_.values(this.charts).forEach(c => c.focus(id));
},
onmouseout: () => {
_.values(this.charts).forEach(c => c.revert());
},
},
},
title: {
text: `${airport.iata}, ${airport.city}, ${airport.state}`,
},
});
});
// Hide all the visible elements of TITLE except for the legend
const placeholder = d3.select(this.charts.TITLE.element);
placeholder.select("g").style("display", "none");
placeholder.select(".c3-title").style("display", "none");
}
init() {
d3_queue.queue()
.defer(d3.json, "airports.json")
.defer(d3.json, "data.json")
.await((error, airports, data) => {
if (error) { alert(error); }
this.initSvg();
this.initAirports(airports);
this.initLinePlot(data);
});
}
}
new PlaneVisualisation().init();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Aeroplane Flights</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"></script>
<script src="https://d3js.org/d3-queue.v2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.11/c3.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.11/c3.min.css">
<style>
body {
font-family: Helvetica;
}
.c3-title {
font-size: 18px;
}
.title {
position: absolute;
top: 0px;
left: 50px;
}
.legend-title {
position: absolute;
top: 130px;
left: 70px;
}
</style>
</head>
<body>
</body>
<script src="index.js"></script>
</html>