Public
Edited
Oct 24, 2022
1 star
Insert cell
Insert cell
Insert cell
Insert cell
map = {
// a "projection" lets us visualize the 3d Earth surface in 2d (going from latitude/longitutde to x/y pixel positions)

const image_orig = 10
var current_country = ""
const projectionTranslator = d3.geoNaturalEarth1()
.fitSize([width, height], countries); // and try to fit it into the space nicely

// setup our project to be able to draw our geographic state outlines (aka paths - a series of points connected by lines)
const pathRenderer = d3.geoPath().projection(projectionTranslator);

// setup a wrapper node to hold our map (with SVG, which knows how to draw paths)
const svg = d3
.select(DOM.element('svg'))
.attr('width', width)
.attr('height', height)
.attr('style', "font-family: 'Lato';")


// define the clipPath
svg.selectAll("clipPath") // define a clip path
.data(countries.features)
.join("clipPath")
.attr("id", d => d.properties.NAME.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g, ''))
.attr("test", d=> console.log(d.properties.NAME.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g, '')))
.append("circle") // shape it as an ellipse
.attr('cx', d => pathRenderer.centroid(d)[0])
.attr('cy', d => pathRenderer.centroid(d)[1])
.attr("r", (width/image_orig)/2) // set the x radius

const relevant_countries = countries.features.filter(function(d) { return d.properties.NAME in countryLookup; })
// draw each state
const map = svg.append("g").attr("id", "map") // setup a container <g> node to hold each state
map.selectAll("path")
.data(countries.features) // the features contain the name of the state and the path to draw its outline
.join('path') // we'll draw an SVG <path> node for each state in the dataset
.attr("d", d => pathRenderer(d)) // this will call the projection function with each state's path
.attr("fill", d=> d.properties.NAME == current_country? "blue": "#eee")
.attr("stroke", "black")
.attr("stroke-width", 1)
.on("mouseover", (event, d) => (leafMouseOverCountry(event, d), event.stopPropagation()))
.on("mouseout", (event, d) => (leafMouseOutCountry(event, d), event.stopPropagation()))
.on("touchstart", (event, d) => leafMouseOver(event, d))
.on("touchend", (event, d) => (leafMouseOutCountry(event, d), event.stopPropagation()))

// and now draw a label for each state too

map
.selectAll("image")
.data(relevant_countries) // we're still working with the same geographic data
.join("image")
.attr("xlink:href", function(d) { return d.properties.NAME in countryLookup? countryLookup[d.properties.NAME].scr: ""})
// .attr("width", function(d) { return d.properties.NAME in countryLookup? width/10: 0})
.attr("height", function(d) { return d.properties.NAME in countryLookup? 1.2 *(width/image_orig): 0})
// the path.centroid function is a built in helper where d3 will give you the x/y coordinates for the
// center of a path - this does well in most cases (but not all)
.attr('x', d => pathRenderer.centroid(d)[0] -(width/image_orig)/2 )
.attr('y', d => pathRenderer.centroid(d)[1] - (width/image_orig)/2)
.attr("clip-path", d => `url(#${d.properties.NAME.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g, '')})`)
.on("mouseover", (event, d) => (leafMouseOver(event, d), event.stopPropagation()))
.on("mouseout", (event, d) => (leafMouseOut(event, d), event.stopPropagation()))
.on("touchstart", (event, d) => leafMouseOver(event, d))
.on("touchend", (event, d) => (leafMouseOut(event, d), event.stopPropagation()))
map.selectAll("text")
.data(relevant_countries) // we're still working with the same geographic data
.join("text")
.attr('x', d => pathRenderer.centroid(d)[0])
.attr('y', d => pathRenderer.centroid(d)[1] + (width/image_orig)/2)
.text("Hover here")
.on("mouseover", (event, d) => (leafMouseOver(event, d), event.stopPropagation()))
.on("mouseout", (event, d) => (leafMouseOutText(event, d), event.stopPropagation()))
.on("touchstart", (event, d) => leafMouseOver(event, d))
.on("touchend", (event, d) => (leafMouseOutText(event, d), event.stopPropagation()))

// tooltip + styling
const toolTip = d3.select("body")
.append("div")
.attr('class', 'hover-info')
.style("position", "absolute")
.style("display", "none")
.style("border-radius", "1px")
.style("width", width)
.style("height", "auto")

// tooltip + styling
const toolTip_country = d3.select("body")
.append("div")
.attr('class', 'name-info')
.style("position", "absolute")
.style("display", "none")
.style("border-radius", "1px")
.style("width", "auto")
.style("height", "auto")

function leafMouseOver(event, d){
d3.selectAll(".current").remove()
current_country = d.properties.NAME
const current_countries_data = countries.features.filter(function(d) { return d.properties.NAME== current_country; })
map.selectAll("path") // this will call the projection function with each state's path
.attr("fill", d=> d.properties.NAME == current_country? "darkblue": "#eee")
d3.selectAll('.hover-info')
.style("display", "none" )

d3.selectAll("image")
.transition()
.duration(500)
.style('opacity', .2)
d3.selectAll('.triangle')
.style("display", "none" )
var full_text = countryLookup[d.properties.NAME].long_text
d3.select(event.target)
.attr("fill", "darkred")
map
.selectAll(".current")
.data(current_countries_data) // we're still working with the same geographic data
.join("image")
.attr("class", "current")
.attr("xlink:href", function(d) { return d.properties.NAME in countryLookup? countryLookup[d.properties.NAME].scr: ""})
// .attr("width", function(d) { return d.properties.NAME in countryLookup? width/10: 0})
.attr("height", 150)
.style("display", "block" )
// the path.centroid function is a built in helper where d3 will give you the x/y coordinates for the
// center of a path - this does well in most cases (but not all)
.attr('x', 0)
.attr('y', 0)
toolTip
.style('top', `${event.pageY + 5}px`)
.style('left', `0px`)
.style('z-index', 100)
.style("background-color", "white")
.html(
`${full_text}`,
)
.style("display", "block")
.append('div')
.attr('class', 'triangle')
.style("background-color", "white")

//https://observablehq.com/@kthieb/interactive-map-to-visualize-world-population-data-tutori for triangle arrow
d3.selectAll('.triangle')
.style('top', `${-Math.sqrt(20) - 3}px`)
.style('left', `${event.pageX }px `)

// d3.select(`#${d.properties.NAME.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g, '')}`)
// .select('circle')
// .attr("r", (width))
d3.select(event.target)
// .attr("height", width/image_orig)
// .attr('x', d => pathRenderer.centroid(d)[0] - (width/image_orig) )
// .attr('y', d => pathRenderer.centroid(d)[1] - (width/image_orig) -10)
.style('opacity', .9)
}

function leafMouseOut(event, d){
d3.select(event.target)
.attr("fill", "#eee")
d3.selectAll("image")
.style('opacity', 1)
d3.selectAll(".current").remove()

// d3.select(`#${d.properties.NAME.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g, '')}`)
// .select('circle')
// .attr("r", (width/image_orig)/2)
// d3.select(event.target)
// .attr("height", 1.5 *width/image_orig)
// // the path.centroid function is a built in helper where d3 will give you the x/y coordinates for the
// // center of a path - this does well in most cases (but not all)
// .attr('x', d => pathRenderer.centroid(d)[0] -(width/image_orig)/2 )
// .attr('y', d => pathRenderer.centroid(d)[1] - (width/image_orig)/2)
// .style('opacity', 1)
}
function leafMouseOutText(event, d){

d3.selectAll("text")
.attr("fill", "black")

d3.selectAll("image")
.style('opacity', 1)
// d3.select(`#${d.properties.NAME.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s+/g, '')}`)
// .select('circle')
// .attr("r", (width/image_orig)/2)
// d3.select(event.target)
// .attr("height", 1.5 *width/image_orig)
// // the path.centroid function is a built in helper where d3 will give you the x/y coordinates for the
// // center of a path - this does well in most cases (but not all)
// .attr('x', d => pathRenderer.centroid(d)[0] -(width/image_orig)/2 )
// .attr('y', d => pathRenderer.centroid(d)[1] - (width/image_orig)/2)
// .style('opacity', 1)
d3.selectAll(".current").remove()
}
function leafMouseOverCountry(event, d){
d3.select(event.target)
.attr("fill", "#eee")

d3.selectAll("image")
.style('opacity', 1)
var full_text = d.properties.NAME

toolTip
.style("display", "none" )
d3.select(event.target)
.attr("fill", "darkblue")
toolTip_country
.style('top', `${event.pageY + 20}px`)
.style('left', `${event.pageX + 20}px`)
.html(
`${full_text}`,
)
.style("display", "block")
.append('div')
.attr('class', 'triangle')

//https://observablehq.com/@kthieb/interactive-map-to-visualize-world-population-data-tutori for triangle arrow
d3.selectAll('.triangle')
.style('top', `${-Math.sqrt(20) - 3}px`)
.style('left', `${event.pageX - 20}px `)

d3.selectAll(".current").remove()

}

function leafMouseOutCountry(event, d){
d3.selectAll("image")
.style('opacity', 1)
toolTip
.style("display", "none" )
d3.selectAll('.hover-info')
.style("display", "none" )
d3.selectAll('.name-info')
.style("display", "none" )
d3.selectAll('.triangle')
.style("display", "none" )
d3.select(event.target)
.attr("fill", "#eee")

d3.selectAll(".current").remove()
}

return svg.node();
}
Insert cell
Insert cell
// this is a topojson file which encodes the outline of each state, along with its name
// we *could* attach this an load it via the Observable `FileAttachment` helper, but we happen to know it's URL online so we can
// load it directly
pathsForMap = d3.json("https://s3-us-west-2.amazonaws.com/s.cdpn.io/25240/us-states.json")
Insert cell
Insert cell
Insert cell
Insert cell
countryData2 = FileAttachment("state-data.csv").csv()
Insert cell
dates = Array.from({length: 8}, (x, i) => i*10 + 1940 )
Insert cell
// function transform_imported data(date) {
// return data.map(d => ({
// name: d.name,
// region: d.region,
// x_axis_value: Date(Date.UTC(d.y_axis_value, 0, 1)),
// y_axis_value: valueAt(d.y_axis_value, date),
// radius_axis_value: valueAt(d.radius_axis_value, date)
// }));
// }
Insert cell
us_foreignaid_greenbook = FileAttachment("us_foreignaid_greenbook.parquet")
Insert cell
import {Scrubber} from "@mbostock/scrubber"
Insert cell
countryData_filtered = significant_events_usaid.filter(d=> (d.year >= date & d.year <= date + 9) | (date == 1940 & d.year <1950))
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell

// build a lookup table so we can go from state name to state abbreviation (for the labels on our map)
countryLookup = {
const lookup = {}
for (const item of countryData_filtered) {
if (item.country in lookup){
lookup[item.country].long_text = lookup[item.country].long_text + "<b>" + item.year + "</b><br><b> " + item.short + "</b>: " + item.long + "<p style='font-size: 10px'> Resources: <a href = 'https://www.amazon.com/Enduring-Struggle-International-Development-Transformation/dp/1538154668' target='_blank'> The Enduring Struggle by John Norris</a>, page " + item.page + " and <a href = " + item.resouces + " target='_blank'> Additional Resourcess</a>" + "<br>Picture text: " + item.wiki_text + "<br><a href=" + item.wiki_pic_page + ">" + "Wikipedia Image Attribution" + "</a></p>"
} else {
lookup[item.country] = {'short': item.short, 'attribution': item.attribution, 'scr': item.wiki_src, 'long_text': "<b>" + item.real_name +": " + item.year + "</b><br><b> " + item.short + "</b>: " + item.long + "<p style='font-size: 10px'> Resources: <a href = 'https://www.amazon.com/Enduring-Struggle-International-Development-Transformation/dp/1538154668' target='_blank'> The Enduring Struggle by John Norris</a>, page " + item.page + " and <a href = " + item.resouces + " target='_blank'> Additional Resourcess</a>" + "<br>Picture text: " + item.wiki_text + "<br><a href=" + item.wiki_pic_page + ">" + "Wikipedia Image Attribution" + "</a></p>" }
}
}
return lookup
}
Insert cell
significant_events_usaid = FileAttachment("significant_events_usaid@5.csv").csv()
Insert cell
Insert cell
data2 = {

let recs = [] // used to store the reorganized records

for (let record of countries.features){
recs.push(record.properties)
}
return recs.sort()
}
Insert cell
Insert cell
countryLookup['Haiti']
Insert cell
Insert cell
//https://observablehq.com/@kthieb/interactive-map-to-visualize-world-population-data-tutori
styles = html`
<style>
.hover-info {
z-index: 10001;
position: absolute;
border: 2px solid black;
border-radius: 4px;
overflow: visible;
}

.triangle {
position: absolute;
z-index: -1;
width: 10px;
height: 10px;
left: 5px;
transform: rotate(45deg);
border-left: 2px solid black;
border-top: 2px solid black;
}
</style>`
Insert cell
viewof toggle = Inputs.toggle({label: "Active", value: false})
Insert cell

// setup a wrapper node to hold our map (with SVG, which knows how to draw paths)
var svg = d3
.select(DOM.element('svg'))
.attr('width', width)
.attr('height', height)
.attr('style', "font-family: 'Lato';")

// define the clipPath
svg.append("clipPath") // define a clip path
.attr("id", "ellipse-clip") // give the clipPath an ID
.append("ellipse") // shape it as an ellipse
.attr("cx", 175) // position the x-centre
.attr("cy", 100) // position the y-centre
.attr("rx", 100) // set the x radius
.attr("ry", 50); // set the y radius

// draw clipped path on the screen
svg.append("rect") // attach a rectangle
.attr("x", 125) // position the left of the rectangle
.attr("y", 75) // position the top of the rectangle
.attr("clip-path", "url(#ellipse-clip)") // clip the rectangle
.style("fill", "lightgrey") // fill the clipped path with grey
.attr("height", 100) // set the height
.attr("width", 200); // set the width

Insert cell
countries = topojson.feature(map_data, map_data.objects.countries)
Insert cell
import {map_data} from "@mcmcclur/world-population-bubble-map"
Insert cell
import {wrap} from "b747cf57f652b537"
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