Published
Edited
Jun 29, 2021
1 fork
Importers
Comments locked
1 star
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
Insert cell
monarchs = d3.csvParse(await FileAttachment("butterflies.csv").text())
Insert cell
Insert cell
monarchsNA2017 = {
let monarchsNA2017 = [];
monarchs.forEach(function(monarch){
if(
(monarch.year == "2017")
&&
(monarch.countryCode == "US" || monarch.countryCode == "MX" || monarch.countryCode == "CA")
){
monarchsNA2017.push(monarch)
};
})

return monarchsNA2017;
}
Insert cell
Insert cell
Insert cell
map = {
//svg variables
let width = 720;
let height = 360;

//variable to hold current viewing mode
let mode = 0;

//create SVG artboard
let svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height);

let bg = svg
.append('rect')
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height);

//scales for converting geographic coordinates to pixels
let longitudeScale = d3
.scaleLinear()
.domain([-150, -60])
.range([0, width]);
let latitudeScale = d3
.scaleLinear()
.domain([10, 55])
.range([height, 0]);

//scale for converting months to a number between 0 and 1
let monthScale = d3
.scaleLinear()
.domain([1, 12])
.range([0, 1]);

//find all the butterfly visualizations (which have not been drawn!)
svg
.selectAll('.butterflies')
//load data
.data(monarchsNA2017)
.enter() //for loop replacement. each data point that is *not* matched in our SVG code is passed forward
.append("circle")
.attr("cx", d => longitudeScale(parseFloat(d.decimalLongitude)))
.attr("cy", d => latitudeScale(parseFloat(d.decimalLatitude)))
.attr('r', 1.4)
.attr('fill', d => d3.interpolatePlasma(monthScale(parseInt(d.month))))
.attr('opacity', .5)
.attr('class', 'butterflies');

//Fanciness for animation below here, feel free to disregard for now.
//but it's a forecast of what is to come!
//tell SVG element to listen for clicks.
svg.on('click', function() {
if (mode == 0) {
//animate butterfly circles moving to center
svg
.selectAll('.butterflies')
.data(monarchsNA2017)
.transition()
.duration(5000)
.delay((d, i) => i * 100)
.attr('cx', d => width / 2)
.attr('fill', d =>
d3.interpolateViridis(monthScale(parseInt(d.month)))
);
//update variable to keep track of mode
mode = 1;
} else {
//reset butterfly circles
svg
.selectAll('.butterflies')
.data(monarchsNA2017)
.transition()
.duration(5000)
.delay((d, i) => i * 100)
.attr("cx", d => longitudeScale(parseFloat(d.decimalLongitude)))
.attr('fill', d => d3.interpolatePlasma(monthScale(parseInt(d.month))));
mode = 0;
}
});

//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
Insert cell
latitudes = {
let latitudes = []
//using the old way, why not? We don't have anything to forEach-iterate over.
for(let i = 0; i<90; i++){
latitudes.push({start:i, end:i+1, butterflies:[]})
}
monarchsNA2017.forEach(function(monarch){
let latitude = Math.floor( parseFloat(Math.abs(monarch.decimalLatitude)) )
latitudes[latitude].butterflies.push(monarch)
})

latitudes.forEach(function(lat){
lat.meanLongitude = d3.mean(lat.butterflies, d => parseFloat(d.decimalLongitude).toFixed(2))
})
return latitudes
}
Insert cell
{
//svg variables
let width = 720;
let height = 360;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

let bg = svg.append('rect')
.attr("x", 0)
.attr("y", 0)
.attr("width",width)
.attr("height",height)
//scales for converting geographic coordinates to pixels
let longitudeScale = d3.scaleLinear().domain([-150,-60]).range([0,width]);
let latitudeScale = d3.scaleLinear().domain([10,55]).range([height,0]);

//scale for converting months to a number between 0 and 1
let monthScale = d3.scaleLinear().domain([1,12]).range([0,1]);
svg.selectAll('.butterflies')
.data(latitudes)
.enter()
.append("circle")
.attr("cx", d => longitudeScale(parseFloat(d.meanLongitude)))
.attr("cy", d => latitudeScale(parseFloat(d.start)))
.attr('r', 1.4)
.attr('fill', 'red')
.attr('opacity', 1)
.attr('class','butterflies')
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
months = {
let months = []
//again, nothing to forEach over, so we need to build up the data structure first.
for(let i = 1; i< 13; i++){
months.push({month:i, butterflies:[]})
}
monarchsNA2017.forEach(function(monarch){
let bfMonth = parseInt(monarch.month);
months[bfMonth - 1].butterflies.push(monarch);
})
months.forEach(function(month){
month.meanLongitude = d3.mean(month.butterflies, d => parseFloat(d.decimalLongitude))
month.meanLatitude = d3.mean(month.butterflies, d => parseFloat(d.decimalLatitude))
})
return months
}
Insert cell
{
//svg variables
let width = 720;
let height = 360;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

let bg = svg.append('rect')
.attr("x", 0)
.attr("y", 0)
.attr("width",width)
.attr("height",height)
//scales for converting geographic coordinates to pixels
let longitudeScale = d3.scaleLinear().domain([-150,-60]).range([0,width]);
let latitudeScale = d3.scaleLinear().domain([10,55]).range([height,0]);

//scale for converting months to a number between 0 and 1
let monthScale = d3.scaleLinear().domain([1,12]).range([0,1]);
svg.selectAll('.butterflies')
.data(months)
.enter()
.append("circle")
.attr("cx", d => longitudeScale(parseFloat(d.meanLongitude)))
.attr("cy", d => latitudeScale(parseFloat(d.meanLatitude)))
.attr('r', 3)
.attr('fill', (d,i) => d3.interpolatePlasma(monthScale(i)) )
.attr('opacity', 1)
.attr('class','butterflies')
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
states = {
//create collector
//here, we can't build the data structure first -- we're not sure what states are represented in the dataset, so we can't just assume 50. Compare this with the latitude and months examples above, where the structure is known and can be created in advance of encountering the data.
let states = {}

//loop through sightings and record each new state and keep track of each
monarchs.forEach(function(monarch){
let sightingState = monarch.stateProvince;

if (sightingState in states && monarch.countryCode == "US"){
//it's a state we have already encountered
states[sightingState].push(monarch);
}
else if (monarch.countryCode == "US"){
//it's a new state
states[sightingState] = [monarch];
}
else{
//other countries states/territories could be handled here...
}
})
//uncomment this line to see the in-progress collector
//return states;
//create a list of objects, one per state, from our single object
let stateDataset = [];
for (let property in states) {
stateDataset.push( {name:property, sightings:states[property]} )
}

//look through the collected states and log how many butterflies there have been
//you could do even further parsing now to separate by month/year/organization...
stateDataset.forEach(function(state){
state.count = state.sightings.length
})
return stateDataset
}
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more