Published
Edited
Dec 12, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@7", "d3-sankey@0.12")
Insert cell
import {legend, Swatches} from "@d3/color-legend"
Insert cell
import { slider, checkbox } from "@jashkenas/inputs"
Insert cell
import { radio, select } from '@jashkenas/inputs';
Insert cell
height = 400
Insert cell
margin = ({top:20, right:30, bottom:30, left:40})
Insert cell
Insert cell
rawData = FileAttachment("Disaster5.csv").csv()
Insert cell
refineData = rawData.map((disasterData) => ({
scope: disasterData.Scope,
year: disasterData.Year,
date: disasterData.Year+"-"+disasterData.Month+"-"+disasterData.Day,
disaster: disasterData.Disaster,
eventName: disasterData.EventName,
country: disasterData.Country,
region: disasterData.Region
}))
Insert cell
scope = groupBy(
refineData.filter((d) => d.year),
"scope"
)
Insert cell
groupBy = (items, key) =>
items.reduce(
(result, item) => ({
...result,
[item[key]]: [...(result[item[key]] || []), item]
}),
{}
)
Insert cell
flatDates = Object.values(scope)
.map((scope) => scope.map((dateWhole) => dateWhole.date))
//remove by city sorting
.flat()
//remove undefined
.filter((d) => d)
Insert cell
flatYears = Object.values(scope)
.map((scope) => scope.map((dateYear) => dateYear.year))
//remove by city sorting
.flat()
//remove undefined
.filter((d) => d)
Insert cell
decades = Object.entries(scope).map(d=>({decade:parseInt(d[0]),events:d[1], count:d[1].length}))
Insert cell
colorScale = d3.scaleOrdinal().domain(['Flood','Drought','Storm','Wildfire','Extreme temperature']).range(['#DD3429','#F38366','#E6E7E8', '#818185', '#393A3B'])
Insert cell
decadeScale = d3.scaleBand().domain(decades.map(d=>d.decade)).range([margin.left,width-margin.right])
Insert cell
decadeScale.bandwidth()
Insert cell
JSON.stringify(refineData)
Insert cell
x1 = d3.scaleBand()
.domain(d3.range(refineData.length))
.range([margin1.left, width - margin1.right])
.padding(0)
Insert cell
margin1 = ({top: 30, right: 0, bottom: 30, left: 40})
Insert cell
height1 = 500
Insert cell
xScale = d3
.scaleTime()
.domain(d3.extent(flatYears))
.range([margin1.left , width - margin1.right])
Insert cell
xAxis1 = (g) =>
g
.attr("transform", `translate(0,${height1 -30})`)
.call(d3.axisBottom(xScale))
Insert cell
styles = html`
<style>

.svg-tooltip {
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background: rgba(255,255,255,1);
border-radius: .1rem;
color: #000000;
display: block;
font-size: 14px;
max-width: 320px;
padding: .2rem .4rem;
position: absolute;
text-overflow: ellipsis;
white-space: pre;
z-index: 300;
visibility: hidden;
}
</style>`
Insert cell
tooltip = {
chart1;

const tooltip = d3
.select("body")
.append("div")
.attr("class", "svg-tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.style('pointer-events','none');

// select all rect
d3.selectAll("rect")
.on("mouseover", function(event, d) {
// change the selection style
d3.select(this)
.raise()
.attr('stroke-width', '2')
.attr("stroke", "black");
// make the tooltip visible and update its text
tooltip
.style("visibility", "visible")
.text(`Date: ${d.date}\nDisaster: ${d.disaster}\n${d.eventName}\nCountry: ${d.country}`); // ${d.x0}\nx1: ${d.x1}`);
})
.on("mousemove", function(event, d) {
tooltip
.style("top", event.pageY - 10 + "px")
.style("left", event.pageX < width/2? event.pageX + 10 + "px" : event.pageX - 150 + "px");
})
.on("mouseout", function() {
// change the selection style
d3.select(this).lower().attr('stroke-width', '0');

tooltip.style("visibility", "hidden");
})
.on("click", function() {
d3.select(this)
.attr("xlink:href", "http://en.wikipedia.org/wiki/")
});
}
Insert cell
Insert cell
d3Sankey = require("d3-sankey")
Insert cell
importData = FileAttachment("untitled-10-1.json").json()
Insert cell
//Read about styling options available at https://github.com/d3/d3-sankey
sankey = d3Sankey
.sankey()
.nodeWidth(30)
.nodePadding(10)
.linkSort(d3.ascending)
.extent([
[horizontalMargin, verticalMargin],
[width - horizontalMargin, height3 - verticalMargin]
])
Insert cell
nodes = importData
.map((d) => ({ name: d.Hometown + " " + d.year }))
.concat([{ name: "United States" }])
.sort((a, b) =>
d3.descending(a.Hometown + " " + a.year, b.Hometown + " " + b.year)
)
Insert cell
Insert cell
sankeyData = {
const sD = {
nodes: nodes,
links: links
};

sD.links.map((link) => {
link.source = sD.nodes.findIndex((node) => node.name == link.source);
link.target = sD.nodes.findIndex((node) => node.name == link.target);
});

return sankey(sD);
}
Insert cell
height3 = 600
Insert cell
horizontalMargin = 30
Insert cell
verticalMargin = 10
Insert cell
colors= ({Mexico:"#DD3429", Guatemala:"#F38366", "El Salvador":"#E6E7E8"})

// Swatches(d3.scaleOrdinal(['Flood','Drought','Storm','Wildfire','Extreme temperature'], (['#DD3429','#F38366','#E6E7E8', '#818185', '#393A3B'])));
Insert cell
Insert cell
mData = d3.csvParse(await FileAttachment("Migration Total-2.csv").text(), d3.autoType)
Insert cell
cData = d3.csvParse(await FileAttachment("Climate Total-2.csv").text(), d3.autoType)
Insert cell
bool_func= { var v
if(dataSelector == 'mData')
{return mData}
else{} return cData }
Insert cell
x = d3.scaleTime()
.domain(d3.extent(bool_func, d => d.Year))
.range([margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(bool_func, d=> d.Total)])//.nice()
.range([height - margin.bottom, margin.top])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(widthBar / 100).tickSizeOuter(0))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(mData.y))
Insert cell
bisect = {
const bisect = d3.bisector(d => d.Year).left;
return mx => {
const Year = x.invert(mx);
const index = bisect(mData, Year, 1);
const a = Year[index - 1];
const b = Year[index];
return Year - a.Year > b.Year - Year ? b : a;
};
}
Insert cell
function transition(path) {
path.transition()
.duration(2000)
.attrTween("stroke-dasharray", tweenDash)
.on("end", () => { d3.select(this).call(transition); });
}
Insert cell
function tweenDash() {
const l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function(t) { return i(t) };
}
Insert cell
line = d3.line()
.defined(d => !isNaN(d.Total))
.x(d => x(d.Year))
.y(d => y(d.Total))
Insert cell
dataSelector
Insert cell
function updateData () {
var mData = dataSelector === 'mData' ? mData: cData;
y.domain( d3.extent( dataSelector, datapoint => datapoint.Total ) )
x.domain( d3.extent( dataSelector, datapoint => datapoint.Year ) )
var path = d3.select( '.chart__line' )
.transition()
.duration( 500 )
.attr( 'd', line( mData ) );
}
Insert cell
Insert cell
height2 = 1000
Insert cell
html`
<style>
@keyframes dash {
to {
stroke-dashoffset: -100;
}
}
</style>`
Insert cell
elSalvador = FileAttachment("El Salvador.csv").csv()
Insert cell
mexico = FileAttachment("Mexico.csv").csv()
Insert cell
guatemala = FileAttachment("Guatemala.csv").csv()
Insert cell
//Create an empty array, and combine (concatenate) all migration data together into it
migrationData = [].concat(elSalvador, mexico, guatemala)
Insert cell
//computed us center is confused by Alaska+Hawaii, so here we just set it ourselves
//https://en.wikipedia.org/wiki/Geographic_center_of_the_United_States
usCentroid = [-103.75, 40]
Insert cell
states = FileAttachment("gz_2010_us_040_00_5m.json").json()
Insert cell
sourceScale = d3.scalePoint().domain(supportedSources).range([0, 1])
Insert cell
//To read about what's happening here...
//https://observablehq.com/@observablehq/introduction-to-generators
{
while (animated == true) {
for (const year of supportedYears) {
viewof selectedPeriod.value = year;
viewof selectedPeriod.dispatchEvent(new Event("input"));
yield Promises.delay(5000);
}
}
}
Insert cell
//for converting long-lat to pixels
projection = d3
.geoAzimuthalEqualArea()
.center([-10, -320])
.scale(1200)
//center on the Americas
.rotate([90, 0, 0])
Insert cell
targetScale = d3
.scalePoint()
.domain(states.features.map((d) => d.properties.NAME))
.range([0, 1])
Insert cell
activeMigrationValues = migrationData
.filter((d) => selectedSources.includes(d.Hometown))
.map((d) => d[selectedPeriod])
.map((d) => parseInt(d))
Insert cell
circleScale = d3.scaleLinear().domain(d3.extent(Object.values(totalMigration))).range([0,100])
Insert cell
growScale = d3.scaleLinear().domain(d3.extent(Object.values(totalMigration))).range([1000,5000])
Insert cell
totalMigration = {
const names = statesWithMigrationData.features.map(d=>d.properties.NAME)
const totalMigration = statesWithMigrationData.features
//for loop through states
.map(state=>state.migration
//get value for all decade per country
.map(d=>Object.values(d))
//combine countries
.flat()
//remove words
.filter(e=>Boolean(parseInt(e)))
//convert to numbers
.map(f=>parseInt(f)).reduce((acc,val)=>acc+val,0))


return names.reduce((acc,val,index) => { acc[val] = totalMigration[index]; return acc }, {} )

}
Insert cell
parseInt("Mexico")
Insert cell
[10,3,2].reduce((acc,val)=>acc+val,0)
Insert cell
statesWithMigrationData = ({
type: "FeatureCollection",
features: states.features
.map((state) => {
return {
...state,
migration: migrationData.filter(
(d) => d.Destination == state.properties.NAME
)
};
})
.map((state) => ({
...state,
centroid: d3.geoCentroid(state),

trails: supportedGeoData.map((country) => ({
type: "Feature",
properties: {
source: country.properties.name,
target: state.properties.NAME,
migration: migrationData
.filter((d) => d.Destination == state.properties.NAME)
.filter((d) => d.Hometown == country.properties.name)[0],
distance: d3.geoDistance(
d3.geoCentroid(state),
d3.geoCentroid(country)
)
},
geometry: {
type: "LineString",
coordinates: [d3.geoCentroid(country), d3.geoCentroid(state)]
}
}))
}))
})
Insert cell
parseInt(
statesWithMigrationData.features[0].migration.filter(
(d) => d.Hometown == "Mexico"
)[0]["2010"]
)
Insert cell
migrationScale = d3
.scaleLinear()
.domain(
d3.extent(
migrationData.filter((d) => selectedSources.includes(d.Hometown)),
(d) => parseInt(d[selectedPeriod])
)
)
.range([0, 1])
Insert cell
//show min and maxes to ensure everything is working
d3.extent(
migrationData.filter((d) => selectedSources.includes(d.Hometown)),
(d) => parseInt(d[selectedPeriod])
)
Insert cell
html`
<style>
@keyframes dash {
to {
stroke-dashoffset: -100;
}
}
</style>`
Insert cell
// viewof country = Inputs.radio(d3.group(refineData, d => d.country))
Insert cell
//Find unique source countries from concatenated array
supportedSources = Array.from(new Set(migrationData.map((d) => d.Hometown)))
Insert cell
supportedGeoData = supportedSources.map((d) =>
countries.features.find((e) => e.properties.name == d)
)
Insert cell
northAmerica = ({
type: "FeatureCollection",
features: countries.features
.filter((d) => d.properties.continent == "North America")
.filter((d) => d.properties.name != "United States")
})
Insert cell
countries = FileAttachment("custom.geo-3.json").json()
Insert cell
//Find all different column headers from first row in CSV file, and then remove the two that aren't time-related
supportedYears = Object.keys(migrationData[0])
.filter((d) => d != "Hometown")
.filter((d) => d != "Destination")
Insert cell
//for how many seconds the animaton will take, based on population
durationScale = d3.scaleLinear().domain(activeMigrationValues).range([20, 10])
Insert cell
fakeScale = d3.scaleOrdinal().domain(supportedSources).range(["#F9D9D6","#F08D82","#F4B1AB"])
// Swatches(d3.scaleOrdinal(['Flood','Drought','Storm','Wildfire','Extreme temperature'], (['#DD3429','#F38366','#E6E7E8', '#818185', '#393A3B'])));
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more