Published
Edited
Jun 29, 2021
1 fork
Comments locked
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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
monarchs
Insert cell
Insert cell
Insert cell
{
//svg variables
let width = 720;
let height = 360;
//dataset cut
let dataLimit = 2500;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//loop through all items in dataset
for (let i = 0; i < dataLimit; i=i+1){
//cast to number, shift by 180 to remove negative numbers, double for desired size
let xPosition = (parseFloat(monarchs[i].decimalLongitude) + 180) * 2

//cast to number, shift by 90 to remove negative numbers, double for desired size
//subtract from height to flip upside down -- big latitudes become small, small become big
let yPosition = height - (parseFloat(monarchs[i].decimalLatitude) + 90) * 2

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", yPosition)
.attr('r', 1)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '.5')
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
{
//svg variables
let width = 800;
let height = 100;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//simple data
let exampleData = [9,2,7.1,5,0,1,6,3.15,-3,13];
//create scale to convert data to pixels
let exampleScale = d3.scaleLinear().domain([0,10]).range([200,600]);
//draw horizontal line
svg.append("line")
.attr("x1", 200)
.attr("y1", height/2)
.attr("x2", 600)
.attr("y2", height/2)
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('stroke-width', '2')
;

//loop through all items in dataset
for (let i = 0; i < exampleData.length; i=i+1){

//convert data to pixels
let xPosition = exampleScale(exampleData[i])

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", height/2)
.attr('r', 5)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '2')
//draw text for each item
svg.append("text")
.attr("x", xPosition)
.attr("y", (height/2) - 20 )
.style('font-family','courier')
.style('font-size',10)
.style('text-anchor','middle')
.text( exampleData[i] + "→" + exampleScale(exampleData[i]))
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
//svg variables
let width = 800;
let height = 100;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//dataset
let exampleData = [1,10,100,1000,10000,100000]
//create scale to convert data to pixels
let exampleScale = d3.scaleLog().domain([1,10000]).range([200,600])
//draw horizontal line
svg.append("line")
.attr("x1", 200)
.attr("y1", height/2)
.attr("x2", 600)
.attr("y2", height/2)
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('stroke-width', '2')
;

//loop through all items in dataset
for (let i = 0; i < exampleData.length; i=i+1){

//convert data to pixels
let xPosition = exampleScale(exampleData[i])

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", height/2)
.attr('r', 5)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '2')
//draw text for each item
svg.append("text")
.attr("x", xPosition)
.attr("y", (height/2) - 20 )
.style('font-family','courier')
.style('font-size',10)
.style('text-anchor','middle')
.text( exampleData[i] + "→" + Math.round(exampleScale(exampleData[i])))
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
//svg variables
let width = 800;
let height = 100;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//dataset
let exampleData = [0,1,2,3,4,5,6,7,8,9,10]
//create scale to convert data to pixels
let exampleScale = d3.scaleLinear().domain([0,10]).range([600,200])
//draw horizontal line
svg.append("line")
.attr("x1", 200)
.attr("y1", height/2)
.attr("x2", 600)
.attr("y2", height/2)
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('stroke-width', '2')
;

//loop through all items in dataset
for (let i = 0; i < exampleData.length; i=i+1){

//convert data to pixels
let xPosition = exampleScale(exampleData[i])

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", height/2)
.attr('r', 5)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '2')
//draw text for each item
svg.append("text")
.attr("x", xPosition)
.attr("y", (height/2) - 20 )
.style('font-family','courier')
.style('font-size',10)
.style('text-anchor','middle')
.text( exampleData[i] + "→" + Math.round(exampleScale(exampleData[i])))
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
//create empty dataset
let exampleData = [];
//fill up random dataset
for (let i = 0; i < 10; i = i+1){
exampleData.push(Math.random()*50);
}

let min = d3.min(exampleData);
let max = d3.max(exampleData);
let mean = d3.mean(exampleData);
let extent = d3.extent(exampleData);

return {minimum: min, maximum: max, average: mean, minmax: extent, data: exampleData}
}
Insert cell
Insert cell
{
//svg variables
let width = 800;
let height = 100;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//create empty dataset
let exampleData = []
//fill up random dataset
for (let i = 0; i < 5; i = i+1){
exampleData.push(Math.random()*50)
}
//extract the min and the max of the dataset
let exampleExtent = d3.extent(exampleData)
//create scale to convert data to pixels
let exampleScale = d3.scaleLinear().domain(exampleExtent).range([200,600])
//draw horizontal line
svg.append("line")
.attr("x1", 200)
.attr("y1", height/2)
.attr("x2", 600)
.attr("y2", height/2)
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('stroke-width', '2')
;

//loop through all items in dataset
for (let i = 0; i < exampleData.length; i=i+1){

//convert data to pixels
let xPosition = exampleScale(exampleData[i])

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", height/2)
.attr('r', 5)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '2')
//draw text for each item
svg.append("text")
.attr("x", xPosition)
.attr("y", (height/2) - 20 )
.style('font-family','courier')
.style('font-size',10)
.style('text-anchor','middle')
.text( exampleData[i].toFixed(1) + "→" + exampleScale(exampleData[i]).toFixed(1))
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
return d3.extent(monarchs, d => d.decimalLongitude)
}
Insert cell
{
return d3.extent(monarchs, d => d.decimalLatitude)
}
Insert cell
Insert cell
{
//svg variables
let width = 720;
let height = 360;
//dataset cut
let dataLimit = 5000;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//scales for converting geographic coordinates to pixels
let longitudeScale = d3.scaleLinear().domain([-180,180]).range([0,width]);
let latitudeScale = d3.scaleLinear().domain([-90,90]).range([height,0]);
//loop through all items in dataset
for (let i = 0; i < dataLimit; i=i+1){
let xPosition = longitudeScale(parseFloat(monarchs[i].decimalLongitude))
let yPosition = latitudeScale(parseFloat(monarchs[i].decimalLatitude))

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", yPosition)
.attr('r', 1)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '.5')
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
//svg variables
let width = 720;
let height = 360;
//dataset cut
let dataLimit = 5000;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//scales for converting geographic coordinates to pixels
let longitudeScale = d3.scaleLinear().domain([-180,0]).range([0,width]);
let latitudeScale = d3.scaleLinear().domain([0,90]).range([height,0]);
//loop through all items in dataset
for (let i = 0; i < dataLimit; i=i+1){
let xPosition = longitudeScale(parseFloat(monarchs[i].decimalLongitude))
let yPosition = latitudeScale(parseFloat(monarchs[i].decimalLatitude))

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", yPosition)
.attr('r', 1)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '.5')
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
//svg variables
let width = 720;
let height = 360;
//dataset cut
let dataLimit = 5000;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//scales for converting geographic coordinates to pixels
let longitudeScale = d3.scaleLinear().domain([-140,-60]).range([0,width]);
let latitudeScale = d3.scaleLinear().domain([0,90]).range([height,0]);
//loop through all items in dataset
for (let i = 0; i < dataLimit; i=i+1){
let xPosition = longitudeScale(parseFloat(monarchs[i].decimalLongitude))
let yPosition = latitudeScale(parseFloat(monarchs[i].decimalLatitude))

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", yPosition)
.attr('r', 1)
.attr('fill', 'orange')
.attr('stroke', 'black')
.attr('stroke-width', '.5')
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
let parameterScale = d3.scaleLinear().domain([0,10]).range([0,1]);
return d3.interpolateViridis(parameterScale(5));
}

Insert cell
Insert cell
{
//svg variables
let width = 800;
let height = 100;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)

//simple data
let exampleData = [1,2,3,4,5,6,7,8,9];
//create scale to convert data to pixels
let pixelScale = d3.scaleLinear().domain([0,10]).range([200,600]);
//create scale to convert data to number between 0 and 1
let parameterScale = d3.scaleLinear().domain([0,10]).range([0,1]);

let scaleNames = ["viridis","magma","cividis"];
for(let i = .25; i <= .75; i=i+.25){
//draw horizontal line
svg.append("line")
.attr("x1", 200)
.attr("y1", height*i)
.attr("x2", 600)
.attr("y2", height*i)
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('stroke-width', '2')
;
//draw label
svg.append("text")
.attr("x", 190)
.attr("y", height*i)
.style('font-family','courier')
.style('font-size',10)
.style('text-anchor','end')
.style('alignment-baseline','middle')
.text(scaleNames[ (i*4) - 1])
;
}
//loop through all items in dataset
for (let i = 0; i < exampleData.length; i=i+1){

//convert data to pixels
let xPosition = pixelScale(exampleData[i])

//convert data to number between 0 and 1
let parameterData = parameterScale(exampleData[i])
//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", height*.25)
.attr('r', 10)
.attr('fill', d3.interpolateViridis(parameterData))
.attr('stroke', 'black')
.attr('stroke-width', '2')
//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", height*.5)
.attr('r', 10)
.attr('fill', d3.interpolateMagma(parameterData))
.attr('stroke', 'black')
.attr('stroke-width', '2')

//draw circle for each item
svg.append("circle")
.attr("cx", xPosition)
.attr("cy", height*.75)
.attr('r', 10)
.attr('fill', d3.interpolateCividis(parameterData))
.attr('stroke', 'black')
.attr('stroke-width', '2')
}
//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
{
//svg variables
let width = 720;
let height = 360;

//dataset cut
let dataLimit = 5000;

//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]);

//loop through all items in dataset
for (let i = 0; i < dataLimit; i = i + 1) {
let xPosition = longitudeScale(parseFloat(monarchs[i].decimalLongitude));
let yPosition = latitudeScale(parseFloat(monarchs[i].decimalLatitude));

//draw circle for each item
svg
.append("circle")
.attr("cx", xPosition)
.attr("cy", yPosition)
.attr('r', 1.4)
.attr(
'fill',
d3.interpolatePlasma(monthScale(parseInt(monarchs[i].month)))
)
.attr('opacity', .7);
}

//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
{
let year2015 = 0;
let year2016 = 0;
let year2017 = 0;
let year2018 = 0;
let year2019 = 0;
let year2020 = 0;
let yearOther = 0;
for(let i = 0; i<monarchs.length;i++){
if (monarchs[i].year == 2015){
year2015 ++
}
else if (monarchs[i].year == 2016){
year2016 ++
}
else if (monarchs[i].year == 2017){
year2017 ++
}
else if (monarchs[i].year == 2018){
year2018 ++
}
else if (monarchs[i].year == 2019){
year2019 ++
}
else if (monarchs[i].year == 2020){
year2020 ++
}
else{
yearOther ++
}
}

return {2015:year2015, 2016:year2016, 2017:year2017, 2018:year2018, 2019:year2019, 2020:year2020, other:yearOther }

}
Insert cell
Insert cell
{
let seasons = [{name:"spring", count:0},
{name:"summer", count:0},
{name:"autumn", count:0},
{name:"winter", count:0}
];
for(let i = 0; i<monarchs.length; i++){
//if the month is *either* 3, 4, or 5...
if (monarchs[i].month == "3" || monarchs[i].month == "4" || monarchs[i].month == "5"){
seasons[0].count++
}
else if ((monarchs[i].month == "6") || (monarchs[i].month == "7") || (monarchs[i].month == "8")){
seasons[1].count++
}
else if ((monarchs[i].month == "9") || (monarchs[i].month == "10") || (monarchs[i].month == "11")){
seasons[2].count++
}
else if ((monarchs[i].month == "12") || (monarchs[i].month == "1") || (monarchs[i].month == "2")){
seasons[3].count++
}
}
return seasons
}
Insert cell
Insert cell
Insert cell
{
//create collector
let months = {}

//loop through sightings
for(let i = 0; i<monarchs.length;i++){
//create a string like "month1" for January.
//We need to do this since JS objects can't have keys beginning with numbers
let sightingMonth = "month" + monarchs[i].month;

//check if we have encountered this month already
if (sightingMonth in months ){
//we have this month already, so make its count 1 bigger.
months[sightingMonth]++
}
else {
//it's a new month, so we add it as a key to our collector object
//a common mistake would be setting it to 0, but then we're not counting the first sighting!
months[sightingMonth] = 1;
}
}
return months
}
Insert cell
Insert cell
{
let institutionalCodes = {}

for(let i = 0; i<monarchs.length;i++){
let institution = monarchs[i].institutionCode;
if (institution in institutionalCodes ){
institutionalCodes[institution]++
}
else {
institutionalCodes[institution] = 1;
}
}
return institutionalCodes
}
Insert cell
Insert cell
{
let institutionalCodes = {}

for(let i = 0; i<monarchs.length;i++){
let institution = monarchs[i].institutionCode;
if (institution in institutionalCodes ){
institutionalCodes[institution]++
}
else {
institutionalCodes[institution] = 1;
}
}
//create an empty array to hold data
let instDataset = [];
//loop through the keys in the single object
for (let property in institutionalCodes) {
//create a new object and push it into array
instDataset.push( {name:property, count:institutionalCodes[property] } )
}

return instDataset
}
Insert cell
Insert cell
{
let countries = {}

for(let i = 0; i<monarchs.length;i++){
let country = monarchs[i].countryCode;
if (country in countries){
countries[country]++
}
else {
countries[country] = 1;
}
}
let countryDataset = [];
for (let property in countries) {
countryDataset.push( {name:property, count:countries[property] } )
}

return countryDataset
}
Insert cell
Insert cell
{
//collector counting off the months
let months = {}

//look over
for(let i = 0; i<monarchs.length;i++){
let sightingMonth;
//check if we need to add a zero in front of the month for Jan-Sept.
//so 5>05 but 11->11
if (parseInt(monarchs[i].month) < 10){
sightingMonth = monarchs[i].year + "0" + monarchs[i].month;
}
else{
sightingMonth = monarchs[i].year + monarchs[i].month;
}
//check if we have seen this month+year combo before
if (sightingMonth in months){
//if we have seen it already, add one
months[sightingMonth]++
}
else {
//otherwise, make a new key
months[sightingMonth] = 1;
}
}
//create an arrays to organize data
let butterfliesByMonth = [];
for (let property in months) {
butterfliesByMonth.push(
{name:property, count:months[property]}
)
}
//sort months by their name - weird and not covered in the tutorial, but should make some intuitive sense?
//looks like an accessor function, right?
butterfliesByMonth.sort((a, b) => {
return parseInt(a.name) - parseInt(b.name);
});
//svg variables
let width = 800;
let height = 400;
let margin = 25;
//create SVG artboard
let svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width",width)
.attr("height",height)
.attr("fill","#ddd")

//create SVG background
let bg = svg.append("rect")
.attr('x',0)
.attr('y',0)
.attr("width",width)
.attr("height",height)
//accessor function to find min and max counts
let monthMinMax = d3.extent(butterfliesByMonth, d => d.count);

//scales for counts to pixels and parameters
let monthSizeScale = d3.scaleLinear().domain(monthMinMax).range([margin,height-(margin*2)]);
let monthParameterScale = d3.scaleLinear().domain(monthMinMax).range([0,1]);

//how wide should each bar be?
let barWidth = (width - (margin * 2)) / butterfliesByMonth.length;
//loop through all items in dataset
for (let i = 0; i < butterfliesByMonth.length; i++){
// for converting data to pixels
let barLength = monthSizeScale(parseFloat(butterfliesByMonth[i].count))
// for converting data to parameter between 0 and 1
let barParameter = monthParameterScale(parseFloat(butterfliesByMonth[i].count))
//draw rectangle for each month
svg.append("rect")
.attr("x", (i*barWidth) + margin)
//pesky needing to flip vertically
.attr("y", (height - barLength) - margin)
.attr('width', barWidth)
.attr('height', barLength)
.attr('fill', d3.interpolatePlasma( barParameter))

//check if we are seeing a January...
if ( butterfliesByMonth[i]["name"].slice(4) == "01"){
//add a textlabel for the new year
svg.append("text")
.attr("x", (i*barWidth) + margin)
.attr("y", height - (margin/2))
.text( butterfliesByMonth[i]["name"].slice(0,4) )
.attr('fill','black')
.attr('font-family','courier')
.attr('font-size',8)
}
}
//show visualization in Observable
return svg.node();
}

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