|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
|
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
|
|
<style type="text/css"> |
|
|
|
/*svg { |
|
border:1px solid #f0f; |
|
}*/ |
|
.axis.y .tick { |
|
stroke-width:0.5px; |
|
stroke-dasharray: 2px 2px; |
|
stroke-opacity: .5; |
|
} |
|
.axis path { |
|
stroke: none; |
|
} |
|
.line { |
|
stroke: black; |
|
fill: none; |
|
stroke-width: 1.5; |
|
} |
|
|
|
.title { |
|
font: 20px sans-serif; |
|
} |
|
|
|
.line { |
|
stroke: darkgray; |
|
stroke-width: 3 ; |
|
} |
|
|
|
</style> |
|
|
|
<body> |
|
|
|
<div class=buttons><div> |
|
<br /> |
|
|
|
</body> |
|
</body> |
|
|
|
<script> |
|
|
|
var radius = 10 |
|
var titleText = "UFO Sightings in " |
|
var parseTime = d3.timeParse("%m/%Y"); |
|
|
|
// Margin Conventions |
|
var margin = {top: 40, right: 50, bottom: 30, left: 40}; |
|
var width = 900 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
// Define x and y scale |
|
var xScale = d3.scaleTime() |
|
.range([0, width]); |
|
|
|
var yScale = d3.scaleLinear() |
|
.domain([0, 1200]) |
|
.range([height, 0]); |
|
|
|
// Create x and y axis |
|
var xAxis = d3.axisBottom(xScale) |
|
|
|
var yAxis = d3.axisLeft(yScale) |
|
.tickSize(-width); //create gridlines |
|
|
|
d3.csv("ufo.csv", ready) |
|
|
|
function ready(error, data) { |
|
|
|
if (error) return console.warn(error); |
|
|
|
// format data |
|
data.forEach(function(d) { |
|
d.date = parseTime(d.date); |
|
d.count = +d.count; |
|
d.year = d.date.getYear() + 1900 |
|
}); |
|
|
|
window.data = data; |
|
|
|
// Function that allows for the swapping out of data in the plot |
|
function dataSwap(datasetYear) { |
|
|
|
datasetYear = +datasetYear |
|
|
|
thisDataGroup = data.filter(function(d) { return d.year == datasetYear}) |
|
|
|
xScale.domain(d3.extent(thisDataGroup, function(d) { return d.date; })); |
|
|
|
svg.selectAll('.axis').filter('.x').call(xAxis) |
|
|
|
svg.selectAll('.ufoGroup') |
|
.data(thisDataGroup) |
|
.transition() |
|
.attr('transform', function(d) { return 'translate(' + xScale(d.date) + ',' + yScale(d.count) + ')'}) |
|
|
|
svg.selectAll('.line') |
|
.transition() |
|
.attr('d', lineGenerator(thisDataGroup)); |
|
|
|
svg.select(".title") |
|
.text(titleText + datasetYear) |
|
} |
|
|
|
// create array of years for buttons |
|
years = d3.set(data.map(function(d) { return d.year })).values(); |
|
|
|
// create buttons |
|
d3.select('.buttons') |
|
.selectAll('button') |
|
.data(years) |
|
.enter().append('button') |
|
.text(function(d) { return d}) |
|
.on('click', function(d) { |
|
dataSwap(d) |
|
}) |
|
|
|
// Define data we're starting with - just 2016 data |
|
var startData = data.filter(function(d) { return d.year == 2016;}) |
|
|
|
// set domain of y scale |
|
yScale.domain(d3.extent(data, function(d) { return d.count; })); |
|
xScale.domain(d3.extent(startData, function(d) { return d.date; })); |
|
|
|
// create color scale |
|
var colorScale = d3.scaleSequential(d3.interpolateCool) |
|
.domain([0, startData.length-1]) |
|
|
|
// Add title text |
|
svg.append("text") |
|
.attr("class", "title") |
|
.attr("dy", -5) |
|
.text(titleText + startData[0].year) |
|
|
|
// Create x and y axis groups |
|
svg.append("g") |
|
.attr("class","x axis") |
|
.attr("transform","translate(0," + height + ")") |
|
.call(xAxis); |
|
|
|
svg.append("g") |
|
.attr("class","y axis") |
|
.call(yAxis); |
|
|
|
// Create line |
|
var lineGenerator = d3.line() |
|
.x(function(d) { return xScale(d.date)}) |
|
.y(function(d) { return yScale(d.count)}) |
|
.curve(d3.curveCardinal); //makes line 'curvy' |
|
|
|
svg.append('path') |
|
.attr('class', 'line') |
|
.attr('d', lineGenerator(startData)); |
|
|
|
// Create Group that will include circles and text |
|
var ufoGroup = svg.selectAll('.ufoGroup') |
|
.data(startData) |
|
.enter().append('g') |
|
.attr('class', 'ufoGroup') |
|
.attr('transform', function(d) { return 'translate(' + xScale(d.date) + ',' + yScale(d.count) + ')'}) |
|
.on('mouseenter', function(d) { |
|
// append text to circle on hover |
|
d3.select(this) |
|
.append('text') |
|
.attr('dx', radius) |
|
.attr('dy', radius) |
|
.text(d.count) |
|
|
|
// make all circles lower opacity |
|
d3.selectAll('circle') |
|
.style('opacity', 0.5) |
|
|
|
// maintain full opacity only for circle currently moused over |
|
d3.select(this) |
|
.select('circle') |
|
.transition() |
|
.ease(d3.easeElastic) |
|
.duration(1000) |
|
.attr('r', radius*2) |
|
.style('opacity', 1) |
|
}) |
|
.on('mouseleave', function(d) { |
|
|
|
// remove text on mouseleave |
|
d3.select(this) |
|
.select('text') |
|
.transition() |
|
.style('opacity', 0) |
|
.remove() |
|
|
|
// put radius back to normal |
|
d3.select(this) |
|
.select('circle') |
|
.transition() |
|
.ease(d3.easeElastic) |
|
.duration(1000) |
|
.attr('r', radius) |
|
|
|
// opacity back to normal for all circles |
|
d3.selectAll('circle') |
|
.style('opacity', 1) |
|
}); |
|
|
|
// append circle |
|
ufoGroup.append('circle') |
|
.attr('r', radius) |
|
.style('fill', function(d, i) { return colorScale(i); }); //to have color scale change across x data values (time) |
|
|
|
} |
|
</script> |