This chart was created for a talk / workshop given at CodeHer Conference in Washington D.C. in 2017. You can find additional information and slides from the talk in this repository.
This scatter / line chart shows the ufo sightings count per month between 2000 and 2016. It includes the following interactions:
xxxxxxxxxx
<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>
https://d3js.org/d3.v4.min.js